diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e0a2b64..49d5995 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,108 +2,93 @@ name: Build on: [push] jobs: - - build-windows: - runs-on: windows-latest + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest] + arch: [x86, x86_64] + include: + # Windows + - os: windows-latest + arch: x86 + platform: windows + - os: windows-latest + arch: x86_64 + platform: windows + # Linux + - os: ubuntu-latest + arch: x86 + platform: linux + - os: ubuntu-latest + arch: x86_64 + platform: linux steps: - - uses: actions/checkout@v3 - - name: Build Windows QVM - run: | - ./build.bat - env: - ARCHIVE: 1 - - build-linux: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Update and get packages + - uses: actions/checkout@v7 + + - name: Install dependencies + if: ${{ matrix.os != 'windows-latest' }} + shell: bash run: | sudo apt-get update # && sudo apt-get upgrade -y - sudo apt-get install -y build-essential - sudo apt-get install -y libc6-dev-i386 - sudo apt-get install -y g++-multilib - sudo apt-get install -y gcc-mingw-w64 - sudo apt-get install -y p7zip-full - - name: Build .dll and .so - run: | - make -j8 release ARCH=x86 - make -j8 release PLATFORM=windows ARCH=x86 - make -j8 debug ARCH=x86 - make -j8 debug PLATFORM=windows ARCH=x86 - make -j8 release ARCH=x86_64 - make -j8 release PLATFORM=windows ARCH=x86_64 - make -j8 debug ARCH=x86_64 - make -j8 debug PLATFORM=windows ARCH=x86_64 - # - name: Get wine32-development package - # run: | - # sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install -y wine32-development - # - name: Build QVM using wine - # run: | - # wine cmd /c build.bat - - name: Build QVM + sudo apt-get install -y build-essential libc6-dev-i386 g++-multilib gcc-mingw-w64 p7zip-full + + - name: Build for ${{ matrix.os }} ${{ matrix.arch }} + shell: bash run: | - sudo chmod 777 tools/bin/linux/* # make all of them executable - make -f MakefileQVM -j8 + if [ "${{ matrix.os }}" == "windows-latest" ]; then + # Windows build steps + ./build.bat + else + # Linux build steps + make -j8 release ARCH="${{ matrix.arch }}" + make -j8 debug ARCH="${{ matrix.arch }}" + + # that also works: + make -j8 release PLATFORM=windows ARCH="${{ matrix.arch }}" + make -j8 debug PLATFORM=windows ARCH="${{ matrix.arch }}" + + # Get wine32-development package + # sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install -y wine32-development + # Build QVM using wine + # wine cmd /c build.bat + + sudo chmod 777 tools/bin/linux/* # make all of them executable + make -f MakefileQVM -j8 + fi env: ARCHIVE: 1 - name: Store QVM artifacts - uses: actions/upload-artifact@v3 + # store only with Linux x86_64 + if: ${{ matrix.os != 'windows-latest' && matrix.arch != 'x86' }} + uses: actions/upload-artifact@v7 with: name: QVMs path: | *.pk3 if-no-files-found: error retention-days: 5 - - - name: Store Linux x86 .so artifacts - uses: actions/upload-artifact@v3 - with: - name: linux-x86 - path: | - build/release-linux-x86 - build/debug-linux-x86 - if-no-files-found: error - retention-days: 5 - - - name: Store Linux x86_64 .so artifacts - uses: actions/upload-artifact@v3 - with: - name: linux-x86_64 - path: | - build/release-linux-x86_64 - build/debug-linux-x86_64 - if-no-files-found: error - retention-days: 5 - - - name: Store Windows x86 DLL artifacts - uses: actions/upload-artifact@v3 + + - name: Store Linux ${{ matrix.arch }} .so artifacts + if: ${{ matrix.os != 'windows-latest' }} + uses: actions/upload-artifact@v7 with: - name: windows-x86 + name: ${{ matrix.platform }}-${{ matrix.arch }} path: | - build/release-windows-x86 - build/debug-windows-x86 + build/release-${{ matrix.platform }}-${{ matrix.arch }} + build/debug-${{ matrix.platform }}-${{ matrix.arch }} if-no-files-found: error retention-days: 5 - - - name: Store Windows x86_64 DLL artifacts - uses: actions/upload-artifact@v3 + + - name: Store Windows ${{ matrix.arch }} DLL artifacts (compiled on Linux) + if: ${{ matrix.os != 'windows-latest' }} + uses: actions/upload-artifact@v7 with: - name: windows-x86_64 + name: windows-${{ matrix.arch }} path: | - build/release-windows-x86_64 - build/debug-windows-x86_64 + build/release-windows-${{ matrix.arch }} + build/debug-windows-${{ matrix.arch }} if-no-files-found: error - retention-days: 5 - - - name: Publish a release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - files: | - *.pk3 - build/* \ No newline at end of file + retention-days: 5 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8e6b8f9..1c8f79d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,16 @@ *.pk3 .vscode .vscode/* +win32-msvc/.vs +win32-msvc/Backup +win32-msvc/Win32 +win32-msvc/Win64 +win32-msvc/x64 +win32-msvc/bfpq3.sln +win32-msvc/*.vcxproj +win32-msvc/*.vcxproj.filters +win32-msvc/*.vcxproj.user +win32-msvc/*.htm +binaries* build* vm* \ No newline at end of file diff --git a/COPYING b/COPYING deleted file mode 100644 index 94a9ed0..0000000 --- a/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - 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 - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/GPL-2 b/COPYING.txt similarity index 100% rename from GPL-2 rename to COPYING.txt diff --git a/Makefile b/Makefile index a65e177..17a7884 100644 --- a/Makefile +++ b/Makefile @@ -60,9 +60,9 @@ ifneq ($(PLATFORM),$(COMPILE_PLATFORM)) else CROSS_COMPILING=0 - ifneq ($(ARCH),$(COMPILE_ARCH)) - CROSS_COMPILING=1 - endif +ifneq ($(ARCH),$(COMPILE_ARCH)) + CROSS_COMPILING=1 +endif endif export CROSS_COMPILING @@ -115,7 +115,7 @@ ifeq ($(PLATFORM),linux) BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes -pipe - OPTIMIZE = -O2 -fvisibility=hidden -fomit-frame-pointer -ffast-math + OPTIMIZE = -O3 -fvisibility=hidden -fomit-frame-pointer -ffast-math SHLIBEXT=so SHLIBCFLAGS=-fPIC -fvisibility=hidden @@ -170,7 +170,7 @@ ifdef MINGW BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes -pipe - OPTIMIZE = -O2 -fvisibility=hidden -fomit-frame-pointer -ffast-math + OPTIMIZE = -O3 -fvisibility=hidden -fomit-frame-pointer -ffast-math SHLIBEXT=dll SHLIBCFLAGS=-fPIC -fvisibility=hidden @@ -221,7 +221,7 @@ ifeq ($(PLATFORM),windows) BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes -pipe - OPTIMIZE = -O2 -fvisibility=hidden -fomit-frame-pointer -ffast-math + OPTIMIZE = -O3 -fvisibility=hidden -fomit-frame-pointer -ffast-math SHLIBEXT=dll SHLIBCFLAGS=-fPIC -fvisibility=hidden @@ -237,10 +237,6 @@ endif # DLL TARGETS = -ifndef FULLBINEXT - FULLBINEXT=.$(ARCH)$(BINEXT) -endif - ifndef SHLIBNAME SHLIBNAME=$(ARCH).$(SHLIBEXT) endif @@ -369,10 +365,32 @@ makedirs: @if [ ! -d $(B)/game ];then $(MKDIR) $(B)/game;fi @if [ ! -d $(B)/ui ];then $(MKDIR) $(B)/ui;fi + +install: + $(if $(DESTDIR), , $(error DESTDIR is not set! Use 'make install DESTDIR=/your/path')) + @$(MAKE) release + @echo "Installing in $(DESTDIR)..." + cp $(BR)/cgame$(SHLIBNAME) $(DESTDIR)/cgame$(SHLIBNAME) + cp $(BR)/qagame$(SHLIBNAME) $(DESTDIR)/qagame$(SHLIBNAME) + cp $(BR)/ui$(SHLIBNAME) $(DESTDIR)/ui$(SHLIBNAME) + @echo "*************************************************************************************" + @echo " Files compiled with release-$(PLATFORM)-$(SHLIBNAME) are successfully installed! " + @echo "*************************************************************************************" + +install_debug: + $(if $(DESTDIR), , $(error DESTDIR is not set! Use 'make install_debug DESTDIR=/your/path')) + @$(MAKE) debug + @echo "Installing in $(DESTDIR)..." + cp $(BD)/cgame$(SHLIBNAME) $(DESTDIR)/cgame$(SHLIBNAME) + cp $(BD)/qagame$(SHLIBNAME) $(DESTDIR)/qagame$(SHLIBNAME) + cp $(BD)/ui$(SHLIBNAME) $(DESTDIR)/ui$(SHLIBNAME) + @echo "*************************************************************************************" + @echo " Files compiled with debug-$(PLATFORM)-$(SHLIBNAME) are successfully installed! " + @echo "*************************************************************************************" + ############################################################################# ## BASEQ3 CGAME ############################################################################# -# $(B)/cgame/cg_particles.o \ CGOBJ_ = \ $(B)/cgame/cg_main.o \ @@ -389,12 +407,14 @@ CGOBJ_ = \ $(B)/cgame/cg_info.o \ $(B)/cgame/cg_localents.o \ $(B)/cgame/cg_marks.o \ + $(B)/cgame/cg_particles.o \ $(B)/cgame/cg_players.o \ $(B)/cgame/cg_playerstate.o \ $(B)/cgame/cg_predict.o \ $(B)/cgame/cg_scoreboard.o \ $(B)/cgame/cg_servercmds.o \ $(B)/cgame/cg_snapshot.o \ + $(B)/cgame/cg_trails.o \ $(B)/cgame/cg_view.o \ $(B)/cgame/cg_weapons.o \ \ @@ -413,6 +433,7 @@ $(B)/cgame$(SHLIBNAME): $(CGOBJ) QAOBJ_ = \ $(B)/game/g_main.o \ + $(B)/game/ai_bfp.o \ $(B)/game/ai_chat.o \ $(B)/game/ai_cmd.o \ $(B)/game/ai_dmnet.o \ diff --git a/MakefileQVM b/MakefileQVM index 4599ad2..8078f99 100644 --- a/MakefileQVM +++ b/MakefileQVM @@ -31,7 +31,7 @@ ifeq ($(COMPILE_PLATFORM),cygwin) endif ifndef PLATFORM -PLATFORM=$(COMPILE_PLATFORM) + PLATFORM=$(COMPILE_PLATFORM) endif export PLATFORM @@ -53,7 +53,11 @@ endif # SETUP AND BUILD ############################################################################# -PK3 = pak9.pk3 +ifndef DESTDIR + DESTDIR = . +endif + +PK3 = $(DESTDIR)/pak9.pk3 MOUNTDIR = source QADIR = $(MOUNTDIR)/game @@ -76,7 +80,7 @@ QA_SRC = \ g_main $(QADIR)/g_syscalls.asm \ bg_misc bg_lib bg_pmove bg_slidemove \ q_math q_shared \ - ai_dmnet ai_dmq3 ai_team ai_main ai_chat ai_cmd ai_vcmd \ + ai_bfp ai_dmnet ai_dmq3 ai_team ai_main ai_chat ai_cmd ai_vcmd \ g_active g_arenas g_bot g_client g_cmds g_combat g_items g_mem g_misc \ g_missile g_mover g_session g_spawn g_svcmds g_target g_team \ g_trigger g_utils g_weapon \ @@ -88,8 +92,8 @@ QA_SRC = \ CG_SRC = \ cg_main $(CGDIR)/cg_syscalls.asm \ cg_consolecmds cg_draw cg_drawtools cg_effects cg_ents cg_event cg_info \ - cg_localents cg_marks cg_players cg_playerstate cg_predict cg_scoreboard \ - cg_servercmds cg_snapshot cg_view cg_weapons \ + cg_localents cg_marks cg_particles cg_players cg_playerstate cg_predict cg_scoreboard \ + cg_servercmds cg_snapshot cg_trails cg_view cg_weapons \ bg_slidemove bg_pmove bg_lib bg_misc \ q_math q_shared \ diff --git a/README.md b/README.md index 7ae2b4e..50297ac 100644 --- a/README.md +++ b/README.md @@ -1,140 +1,260 @@ Bid For Power (BFP) source code =============================== +[![Stars](https://img.shields.io/github/stars/LegendaryGuard/BFP)](https://github.com/LegendaryGuard/BFP/stargazers) +[![Forks](https://img.shields.io/github/forks/LegendaryGuard/BFP)](https://github.com/LegendaryGuard/BFP/forks) +[![License](https://img.shields.io/github/license/LegendaryGuard/BFP)](#legal) +[![Build actions](https://img.shields.io/github/actions/workflow/status/LegendaryGuard/BFP/build.yml)](https://github.com/LegendaryGuard/BFP/actions) +![Visits](https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fgithub.com%2FLegendaryGuard%2FBFP&label=Visits&countColor=%232ccce4&style=flat) -A legendary 90s era Quake 3 Arena mod. + +A legendary Quake 3 Arena mod from the late 90s to early 2000s.

-bfpq3logo + bfpq3logo

- -## *WARNING!* UNDER CONSTRUCTION! - -### Table of contents -> > 1. [TODO list](#todo-list) -> > 2. [History](#history) -> > 3. [About the repository](#about-the-repository) -> > 4. [References and clues to know how should be the game](#references-and-clues-to-know-how-should-be-the-game) -> > 5. [How to build](#how-to-build) -> > > 5.1. [Introduction](#introduction)
-> > > 5.2. [Windows](#windows) -> > > > 5.1.1. [Building QVM (using .bat)](#building-qvm-using-bat)
-> > > > 5.1.2. [Building QVM (mingw)](#building-qvm-mingw)
-> > > > 5.1.3. [MSYS2 (mingw) (Building dynamic libraries (.dll))](#msys2-mingw-building-dynamic-libraries-dll)
-> > > > 5.1.4. [Cygwin (mingw) (Building dynamic libraries (.dll))](#cygwin-mingw-building-dynamic-libraries-dll) -> > > -> > > 5.3. [Linux](#linux) -> > > > 5.3.1. [Building QVM (make)](#building-qvm-make)
-> > > > 5.3.2. [Building QVM (using .bat)](#building-qvm-using-bat-1)
-> > > > 5.3.3. [Building shared libraries (.so)](#building-shared-libraries-so) -> > > -> > > 5.4. [Optional](#optional) -> > 6. [Notes](#notes) -> > 7. [License](#license) -> > 8. [Credits](#credits) - -# TODO list: - -- [x] ~~Toggeable flight (bind key). Hint: FLIGHT POWERUP~~ -- [ ] Gauntlet logic must be replaced as a bind key, it will use fight animation (using kicks and fists) -- [x] ~~Remove weapon visuals (models and stuff)~~ -- [x] ~~Animations as listed on the old docs~~ -- [x] ~~Bind key to recover ki energy~~ -- [x] ~~Bind key to toggle speed (ki boost). HINT: HASTE POWERUP~~ -- [x] ~~Replace ammo to ki energy stamina~~ -- [x] ~~Third person traceable crosshair~~ -- [ ] Make ki energy regeneration, ki use, attacks, charging balance indicated on old docs -- [ ] Powerlevel and Power Tiers indicated on old docs -- [ ] Hit Stun (makes player can't use ki, melee, block and charge) -- [ ] Power Struggles (when two beam attacks collide) -- [ ] Blocking (consumes ki energy, transfers all damage to ki instead of health, deflect missile attacks, more info on old docs) -- [ ] Short-Range Teleport (when pressing 2 times left or right) -- [ ] Transformations (related to Power Tiers) -- [ ] Attacksets (configurable for cfgs) -- [ ] Skin Config File (explosionModel, explosionShader, missileRotation, missileShader, ... look old docs about that. "Custom plugin models") -- [x] ~~Playable third person mode and first person vis mode (add the options in the UI Setup menu)~~ -- [ ] Cvars as described on old docs -- [ ] Survival gametype (`g_gametype 3`) -- [ ] Oozaru gametype (`g_gametype 4`) -- [ ] Last Man Standing gametype (`g_gametype 6`) -- [ ] 6 different selectable characters, each with 5 attacks (can be referenced to some previous tasks) -- [ ] 21 different ki attacks including controllable, homing, and chargeable attacks (no guns) (can be referenced to some previous tasks) - -### History +# TODO list + +### Pending: + +- [ ] Attacksets ([bfp_attacksets.cfg](cfgs/bfp_attacksets.cfg)) +- [ ] Skin Config File (explosionModel, explosionShader, missileRotation, missileShader, ... look old docs and cfgs about that: Custom plugin models - default.cfg for each player model) +- [ ] Weapon Config File ([bfp_weapon.cfg](cfgs/bfp_weapon.cfg) & [bfp_weapon2.cfg](cfgs/bfp_weapon2.cfg)) + +### Done: + +- [x] Animations as listed on the old docs +- [x] Auras +- [x] Balanced player physics movements (different from the original BFP, but balanced for gameplay) +- [x] Beam trails +- [x] BFP config file ([bfp.cfg](cfgs/bfp.cfg)) (a sample of how the game saves general binding and BFP cvar stuff in a cfg file) +- [x] bg_ weapon properties: weaponTime, randomWeaponTime, kiCostAsPct, kiPct, kiCost, chargeAttack, chargeAutoFire, minCharge, maxCharge, loopingAnim, noAttackAnim and movementPenalty +- [x] Bind key to recover ki energy +- [x] Bind key to toggle speed (ki boost) +- [x] Blinding Flash (blinds opponents during 6 seconds) +- [x] Blocking (consumes ki energy, transfers all damage to ki instead of health, deflect missile attacks, more info on old docs) +- [x] Breakable map entities ("func_breakable") +- [x] Bots behavior +- [x] Bounce features (bounces, bounceFriction, noZBounce) +- [x] Cone of fire feature (coneOfFireX and coneOfFireY) +- [x] Cvars +- [x] Damage calculated by attacker powerlevel, modified knockback and melee knockback (pushing opponents after receiving impact/damage, also loses altitude while flying/underwater/on slopes) +- [x] Charge multipliers and maximum damage for weapons (maxDamage, chargeRadiusMult, chargeDamageMult, chargeExpRadiusMult) +- [x] Extraknockback for weapons (extraKnockback) +- [x] Forcefield feature +- [x] Gametype: Survival (`g_gametype 3`) +- [x] Gametype: Monster (`g_gametype 4`) +- [x] Gametype: Team Last Man Standing (`g_gametype 6`) +- [x] Homing weapon feature +- [x] Hit Stun (makes player can't use ki, melee, block and charge) +- [x] Illegal player model server handling +- [x] Instant character model changing +- [x] Ki shockwave (or Tornado Blast) weapon (a shockwave to push opponents) +- [x] Ki trails +- [x] Load player models only by prefix +- [x] Multishot feature +- [x] Melee feature +- [x] Offset features (randXOffset, randYOffset and alternatingXOffset) +- [x] Particles +- [x] Power Struggles (when two beam attacks collide) +- [x] Powerlevel and Power Tiers indicated on old docs +- [x] Playable third person mode and first person vis mode +- [x] Priority for projectiles +- [x] Missile properties (missileSpeed, missileDuration, missileGravity and missileAcceleration) +- [x] railTrail weapon property +- [x] Radius feature (radius, maxRadius, maxExpRadius) +- [x] Razor disk weapon (fires a cutting disk which can pierce beating low health opponents) +- [x] Reflective feature +- [x] Remove some items like powerups and health pickups +- [x] Remove weapon visuals (models and stuff) +- [x] Replace ammo to ki energy stamina +- [x] Rocket jumping (applicable if jumping is pressed at the same time) +- [x] sbeam attack type feature +- [x] Short-Range Teleport - Zanzoken (when pressing 2 times left or right) +- [x] Spectator can stop following the last player +- [x] Splitting ki ball (rdmissile attack type) feature +- [x] Third person traceable crosshair +- [x] Toggeable flight (bind key) +- [x] Transformations (related to Power Tiers) + + +## Table of contents +> 1. [History](#history) +> 2. [About the repository](#about-the-repository) +> 3. [References and clues to know how should be the game](#references-and-clues-to-know-how-should-be-the-game) +> 4. [How to build](#how-to-build) +> > 4.1. [Introduction](#introduction)
+> > 4.2. [Windows](#windows) +> > > 4.2.1. [Building QVM (using .bat)](#building-qvm-using-bat)
+> > > 4.2.2. [Building QVM (mingw)](#building-qvm-mingw)
+> > > 4.2.3. [MSYS2 (mingw) (Building dynamic libraries (.dll))](#msys2-mingw-building-dynamic-libraries-dll)
+> > > 4.2.4. [Cygwin (mingw) (Building dynamic libraries (.dll))](#cygwin-mingw-building-dynamic-libraries-dll)
+> > > 4.2.5. [MSVC (.dll))](#msvc-dll) +> > +> > 4.3. [Linux](#linux) +> > > 4.3.1. [Building QVM (make)](#building-qvm-make)
+> > > 4.3.2. [Building QVM (using .bat)](#building-qvm-using-bat-1)
+> > > 4.3.3. [Building shared libraries (.so)](#building-shared-libraries-so) +> > +> > 4.4. [Optional](#optional) +> 5. [Notes](#notes) +> 6. [Legal](#legal) +> 7. [Credits](#credits) + +# History ![BFP_ZEQ2_history](https://github-production-user-asset-6210df.s3.amazonaws.com/49716252/267147557-7954d397-3df4-4cf7-b9c3-d62e393658ab.png) -Started: 1999
+Started: [1998](https://goldenhammersoftware.blogspot.com/2010/07/big-mountain-snowboarding-history-and.html)
Ended: 2002 Bid For Power is a total conversion for QuakeIII that plays nothing like the original game. Players take control of Ki-powered superheros and battle it out in a mostly aerial fight. The game is highlighted by the work of a great art team and an original style, and the gameplay is extremely fast paced. It can be difficult to keep up with until you get the hang of it. -The source code was lost, but the assets and some docs are in any place. +The project was started on Quake 2, that's where they were getting organized playing Quake and learn some programming there. -#### Old dev journals (1999 - 2002) +The source code is said to have started from SDK 1.15c released on January 14, 2000. It continued to be updated, including the addition of `cg_particles.c` in 1.29, until 1.31. +More info can be found in [Quake 3 Arena changelog version history](https://discourse.ioquake.org/t/quake-3-changelog-version-history/375). -- Old Yrgol dev journal: https://web.archive.org/web/20020205150340/http://www.bidforpower.com/journals/yrgol.php +The original source code appears to be lost, but the assets and some docs are available in various places. Nonetheless, not all sources are accessible. -Click here to see the [Old Yrgol dev journal (Markdown edition)](docs/yrgol_dev_journal.md) +### Old dev journals (1998 - 2002) -- Old Rodney Olmos dev journal: https://web.archive.org/web/20011218204129/http://bidforpower.com/journals/rodney.php +The original URLs can be slower to load, so it's recommended to use markdown edition ones. -Click here to see the [Old Rodney Olmos dev journal (Markdown edition)](docs/rodney_dev_journal.md) +NOTE: Some URLs and images within these contents may be broken or partially recovered. The final parts of Chris and Yrgol dev journals were found here:
+Chris: https://web.archive.org/web/20011202065630/http://bidforpower.com/
+Yrgol: https://web.archive.org/web/20020520060044/http://www.planetquake.com/Bidforpower/
+On markdown editions, the data log is complete. -- Old PyroFragger dev journal: https://web.archive.org/web/20011218203246/http://bidforpower.com/journals/pyrofragger.php +- Chris dev journal: https://web.archive.org/web/20020210145200/http://bidforpower.com/journals/chris.php -Click here to see the [Old PyroFragger dev journal (Markdown edition)](docs/pyrofragger_dev_journal.md) + * [Chris dev journal (Markdown edition)](docs/old_dev_journals/chris_dev_journal.md) -### About the repository +- Yrgol dev journal: https://web.archive.org/web/20020205150340/http://www.bidforpower.com/journals/yrgol.php -We're making a replica of the lost source code.
-The highest priority goal is copying and recreating completely BFP game logical structure.
-Any fixes, improvements and contributions are welcome. But we can't accept secondary things and other stuff that don't reach the goals. + * [Yrgol dev journal (Markdown edition)](docs/old_dev_journals/yrgol_dev_journal.md) -### References and clues to know how should be the game +- Ansel dev journal: https://web.archive.org/web/20011203063814/http://bidforpower.com/journals/ansel.php -Documentations, references and extracted stuff will give us clues to reach the goals.
+ * [Ansel dev journal (Markdown edition)](docs/old_dev_journals/ansel_dev_journal.md) -- Old documentations: +- Anthony dev journal: https://web.archive.org/web/20020210151755/http://bidforpower.com/journals/anthony.php - * [Guide](docs/Guide.md) - * [Creating custom plugin models](docs/Create_Custom_Models.md) + * [Anthony dev journal (Markdown edition)](docs/old_dev_journals/anthony_dev_journal.md) -
+- Dash dev journal: https://web.archive.org/web/20020223210411/http://www.bidforpower.com/journals/dash.php -- Cvars, cmd and bind stuff about the old game: + * [Dash dev journal (Markdown edition)](docs/old_dev_journals/dash_dev_journal.md) - * [Bindlist](docs/bind_bfp_list.txt) - * [Cmdlist](docs/cmd_bfp_list.txt) - * [Cvarlist](docs/cvar_bfp_list.txt) +- Rodney Olmos dev journal: https://web.archive.org/web/20011218204129/http://bidforpower.com/journals/rodney.php -We can see a cvar, e.g. `g_plKillBonusPct`, which means we need to find the function that rewards the player and do something with that. Something like this: + * [Rodney Olmos dev journal (Markdown edition)](docs/old_dev_journals/rodney_dev_journal.md) -```c -if ( killedSomeone ) { - pl = currentPL + (currentPL * g_plKillBonusPct.value); -} -``` +- PyroFragger dev journal: https://web.archive.org/web/20011218203246/http://bidforpower.com/journals/pyrofragger.php -
+ * [PyroFragger dev journal (Markdown edition)](docs/old_dev_journals/pyrofragger_dev_journal.md) -_**Click on some image to see it complete**._ +- Remisser dev journal: https://web.archive.org/web/20020210152114/http://bidforpower.com/journals/remisser.php -- How should the HUD be: + * [Remisser dev journal (Markdown edition)](docs/old_dev_journals/remisser_dev_journal.md) -hud_display +... And more dev journals can be found [here](https://web.archive.org/web/20011202135731/http://bidforpower.com/journals/). -
+# About the repository -- When player receives a hit stun (`g_hitStun 1`): +We're making a replica of the lost source code.
+The highest priority goal is to copy and recreate the complete logical structure of the BFP game. It would be a game SDK that'll provide a structured and standardized way to modify the mod.
+You'll notice some differences and things that the original Bid For Power didn't have/were forgotten, incomplete or poorly made such as: + +#### EFFECTS: +- how particles move (e.g. bubbles are handled underwater and touching something solid vanishes to save performance) +- explosion dynamic lights are back (these were broken after RC/beta versions) +- real impact crack mark, when projectiles/ki attacks impact something solid on ground, walls or slopes (on original BFP, that hasn't been fixed) +- particle aura is almost implemented +- beam trails are bendy, almost similar to original BFP though +- while charging ki near water, bubble particles appear +- when player is still moving with/without friction and charging ki, antigrav rock particles appear +- when antigrav rock and rock debris particles fall in a slope or inclined places, bounce and slide, that makes the movements more realistic +- 3d particles for bubbles, antigrav rock and rock debris, that can be enabled with `cg_3dparticles` cvar. For 3d rock particles, you need 3 models inside models/effects/ directory: `pebble1.md3`, `pebble2.md3` and `pebble3.md3`; otherwise, it will appear the standard ones. That option is included in BFP OPTIONS menu in VIEW & FX, also with a new explosion type option in EXPLOSIONS: Ultra Hardcore +- piercing weapons, like Razor disk, emit spark particles when hitting a player +- beam struggle emits spark particles in the middle of both beams (in original BFP, emits in the zeroed origin) +- new particles: charge smoke, which appears when charging ki near the ground +- new cool missile trails (that replaces original Q3 missile smoke trails), includes rainbow option (it can be disabled by setting `cg_oldRocket 1`). These trails are quite similar to the Rocket pod weapon from [Bazooka Q3 (BQ3)](https://planetquake.gamespy.com/View10ca.html?view=Articles.Detail&id=693) mod + +#### PMOVE: +- balanced player pmove physics: + - players can interact bounce pads like Q3 does (on original BFP, the physics are like you're sliding heavily on the ground and not bouncing as usually do, you're being pushed when touching bounce pads) + - no weird underwater movements while going intentionally down and moving crazily fast touching the ground (this might be a bug/glitch from original BFP) + - no specified stuck animation, so any animation is correctly handled when being stuck (on original BFP, when the player is stuck or pretty near to something solid, the reason is still unknown though. It does a jumping forward/backward animation, that doesn't make sense) + - water movement handling is different from original BFP, but it works similarly + +#### ATTACKS: +- beam with chargeAutoFire can fire correctly by holding the attack key (on original BFP, beam is detonated at the first instance and player gets damaged by the detonation) +- the owner who fires rdmissile with chargeAutoFire, can fire without dealing damage (on original BFP, every time is being used after some charge, player gets damaged after firing per weaponTime) + +#### GAMEMODES: +- survival gamemode is pretty well balanced (on original BFP, when everytime the player changes a different character model from their own prefix, dies and respawns during warmup, the warmup resets. So, that's unfair) +- monster/oozaru gamemode has the following in-game differences compared to the original: + - the player monster is labeled 'MON' on the scoreboard + - the player monster has a larger floating sprite chat + - the player monster has a larger shadow effect similar to regular players + - the player monster has a large dynamic light when charging or using ki + - the player monster's ki trails are larger than other players' + - the player monster generates bigger bubbles, smoke, and antigrav rock particles when charging/using ki boost + - the player monster can pick up items on the floor or where its hitbox can touch + - the player monster can use teleporter without getting stuck on most maps, but that may not work 100% on all of them + - player monster's first-person and first-person vis mode viewpoints work properly + - player monster's third-person camera has improved focus in the code (similar to BFP's standard third-person view but scaled for giant characters), it can be enabled by setting `BFP_MONSTER_CAMERA_VIEWPOS` macro to `0` and recompile the code, although the traceable crosshair looks a bit jerky + - `g_monster` cvar enables the monster/big monkey feature (that happened in the RC/beta versions) + - available in the UI, maps marked as `monster` besides `oozaru` can be viewed in the GAME SERVER menu +- team last man standing has the following in-game differences compared to the original: + - players cannot switch teams after joining a team (on original BFP, the dead player -who was forced to spectate- can join during the match, that was against the rules) + - players cannot voluntarily switch to spectator after joining a team + - players attempting to switch teams/spectate receive centerprint messages + - maps marked as `tlms` besides `lms` can be viewed in the GAME SERVER menu + +#### UI: +- some adjusted UI buttons +- BFP OPTIONS menu is upgraded, big explosions and smoke options are back (these were removed after RC/beta versions), also shell and ring options are available and these are options are interactive with explosion type option. Sprite and particle aura types are available on aura type option +- in the first GAME SERVER menu has a horizontal scrolling description about the selected gametype +- GAME SERVER menu has pagination +- PLAYER SETTINGS menu has pagination +- SERVER INFO menu displays all server info (on original BFP, the info was badly displayed and nothing was shown) and it has pagination +- DRIVER INFO menu is fixed (on original BFP crashes) and it has pagination on extensions +- CHANGE MAP button is added in in-game pause MENU, allows you to change the map without restarting the server and making everyone reconnect + +#### MISC: +- `cg_drawTeamOverlay 2` is fixed on the lower right corner (on original BFP looks non-visible) +- `g_allowSpectatorChat` cvar is functional, spectators can't send messages if the cvar is disabled +- `cg_forceSkin` forces the default skin for each model correctly when changing models/skins (on original BFP, that changes to the first model of the prefix with default skin) +- some cvars didn't save changed values after quitting the game that happened on original BFP (means that `CVAR_ARCHIVE` wasn't on them), but these are now applied on replica +- kiCharge, boostCost and blockCost cvars work differently +- removed some unused cvars +- spectator mode can toggle ki boost as if the player is flying +- ki boost consumption is applied in msec if `timescale` is different from it (on original BFP only applies until 0.89 and from 0.89 until 0.80 reduces one per one very slowly) +- file size differences between the QVM and the original BFP QVM can be quite significant +- configstrings in CS_PLAYERS like "c1" and "hc" are removed to be optimized, handicap is disabled logically because it isn't used at all +- some Q3e patches are applied, like events and function stuff... +- ... + +Any fixes, improvements and contributions are welcome. But we can't accept secondary things and other stuff that don't reach the goals. -hit_stun_received +# References and clues to know how should be the game + +Documentations, references and extracted stuff will give us clues to reach the goals.
+ +- Old documentations and networking: + + * [Guide](docs/Guide.md) + * [Creating custom plugin models](docs/Create_Custom_Models.md) + * [Networking](docs/bfp_networking.md)
-- When player is being ready to shot (holding a key): +- Attackset and Weapon config files: -ready_to_attack + * [Attackset Config File](docs/bfp_attacksets_config_file.md) + * [Weapon Config File](docs/bfp_weapon_config_file.md)
@@ -157,20 +277,15 @@ Weapon settings: * [bfp_weapon.cfg](cfgs/bfp_weapon.cfg) * [bfp_weapon2.cfg](cfgs/bfp_weapon2.cfg) -BFP config (general binding and some client stuff, unused. WARNING: when executing, game crashes): +BFP config (a sample of how the game saves general binding and BFP cvar stuff in a cfg file): * [bfp.cfg](cfgs/bfp.cfg) -Other q3 config: +# How to build - * [q3config.cfg](cfgs/q3config.cfg) +### Introduction - -### How to build - -#### Introduction - -*IMPORTANT NOTE TO THE DEVELOPMENT*: all source code files should be **UTF-8 without BOM** and **Unix (LF)**, otherwise, it will cause compiler errors when using MakefileQVM (most likely, syntax error will be displayed). +*IMPORTANT NOTE TO THE DEVELOPMENT*: all source code files must be **UTF-8 without BOM** and **Unix (LF)**, otherwise, it will cause compiler errors when using MakefileQVM (most likely, syntax error will be displayed). - `.map` file is a linker map file, which is generated by the linker when it links together multiple object files into an executable or shared library. It contains information about the symbols (such as functions and variables) defined in each object file, as well as their addresses in the final executable or library. The information in the map file can be useful for debugging and performance analysis. For example, it can help you identify which functions are taking the most time to execute, or which functions are being called from which parts of the code. @@ -281,6 +396,26 @@ The information in the map file can be useful for debugging and performance anal 7- `git` 3. Open Cygwin, go to the directory where you cloned the repository and compile with `make` + + * #### _MSVC (.dll)_: + + Requires MSVC (Microsoft Visual C++) 2005 or newer (Visual Studio Community) to be installed. + + 1. Run `bfpq3.sln` + + Open the solution in `win32-msvc` directory. If you're using a newer version of MSVC and it's appearing a dialog box that tries to migrate the solution and their stuff, continue by clicking Ok. After the browser will open about the solution migration details, you can close it and ignore the warnings that comments about the old MSVC stuff from 2005. + + 2. Build the solution + + In the *Solution Configurations* tab, you can switch `Debug` or `Release` and `Win32` or `x64`.
+ Right-click to the solution in the *Solution Explorer* and click `Build Solution`, check the log until the build is succeded. Go to `Win32` or `x64` folder in `win32-msvc`, look inside `Debug` or `Release` and get the dlls you built. + + 3. Debugging + + You need the dlls built with `Debug` and running in-game.
+ In the MSVC tab, `Debug` > `Attach to Process` or press `Ctrl+Alt+P` and use the `Attach to Process` dialog to attach the debugger to the process (in that case select `Quake3.exe` / `ioQuake3.exe` / `Quake3e.exe` or any Quake 3 executable launcher). After, while that's running, add some breakpoint in some line of the code where you want to debug and test. + + Some old MSVC tutorial can be found in **Focus on Mod Programming for Quake III Arena** in `APPENDIX A - Debugging Your Mod in Visual Studio` section. - ### Linux: @@ -311,6 +446,11 @@ The information in the map file can be useful for debugging and performance anal make -f MakefileQVM clean ``` + Optionally, you can set the destination directory, so the pk3 file will appear on this directory: + ```sh + make -f MakefileQVM DESTDIR=/your/path/q3/baseq3mod + ``` + * #### _Building QVM (using .bat)_: 1. The alternative to execute and get the compiled qvms with `build.bat` requires [`wine` package](https://www.winehq.org/). So, in that part, needs the i386 package: @@ -362,18 +502,33 @@ The information in the map file can be useful for debugging and performance anal ... Optionally, you can play the parameters like `ARCH=x86_64` (compiles 64-bits builds), `PLATFORM=windows` (compiles dlls), `PLATFORM=linux` (compiles shared libraries (.so files)) ... + * To compile and copy release builds at the destination directory, `DESTDIR` parameter is mandatory: + ```sh + make install DESTDIR=/your/path/q3/baseq3mod # compiles release builds and copy the builds to the destination directory (you can also put ARCH=x86 PLATFORM=windows if you want) + ``` + + * To compile and copy debug builds at the destination directory, `DESTDIR` parameter is mandatory: + ```sh + make install DESTDIR=/your/path/q3/baseq3mod # compiles debug builds and copy the builds to the destination directory (you can also put ARCH=x86 PLATFORM=windows if you want) + ``` +
-### Notes +# Notes **IMPORTANT NOTE**: This repository was initialized from https://github.com/marconett/q3a. #### Added source code files: +- ai_bfp.c +- ai_bfp.h +- bg_events.h +- bg_meansofdeath.h - cg_cvar.h +- cg_trails.c - g_cvar.h -- ui_cvar.h - ui_bfpoptions.c +- ui_cvar.h - ui_mem.c - ui_mp3decoder.c - ui_mp3decoder.h @@ -389,31 +544,51 @@ The information in the map file can be useful for debugging and performance anal #### Unused source code files and unavailable in the build tools: -- cg_particles.c +- g_rankings.c +- g_rankings.h +- ui_login.c - ui_rankings.c - ui_rankstatus.c - ui_signup.c - ui_specifyleague.c - ui_spreset.c -### License +# Legal -The mod source code is GPLv3 licensed, the source code contents are based on Quake III Arena which is GPLv2 licensed. +The mod source code is [GPLv2 licensed](./COPYING.txt), the contents are based on Quake III Arena. -The ancient abandoned MP3 decoder (`ui_mem.c`, `ui_mem.h`, `ui_mp3decoder.c` and `ui_mp3decoder.h`) is based on code from various contributors: +The ancient abandoned MP3 decoder (`ui_mem.c`, `ui_mem.h`, `ui_mp3decoder.c` and `ui_mp3decoder.h`) uses _Open Source Modification License (OSML) v1.0_ and is based on code from various contributors: - Copyright (C) 1993 Sun Microsystems - Copyright (C) 1995-1997 Michael Hipp - Copyright (C) 1999 Aaron Holtzman - Copyright (C) 2000-2001 Tim Angus -## Credits +The images, screenshots, and URLs, even in the docs, included in this repository are used for reference purposes only and are not covered by the GPL license. They're sourced from various locations and are subject to their respective copyrights and terms of use. + +> [!WARNING] +> Any derivative works must follow the same rules and disclose their code to the public as to encourage fan projects based on Bid For Power. + +### Bid For Power name + +Nobody owns the "Bid For Power" name. Bid For Power was founded by Chris James and likely ended up in the palm of Yrgol's hand (although Yrgol doesn't own the assets). The owner, the maintainer and the contributors of the repository don't own this name.
+This does not give any single person or a group of people to sell the name, basically it belongs to the original community.
+The Bid For Power team may provide sufficient security against any claims or improper use of the name. + +### Disclaimer + +The game elements such as characters, and events depicted in this game are fictitious. Any resemblance to actual persons, living or deceased, or real events is purely coincidental. + +# Credits Bid For Power is made by these staff members. We don't own materials such as art designs, maps and character models from their assets. -#### Bid For Power Staff Members +
+ +### Bid For Power Staff Members +

Ansel
-Skin Artist
+Skin Artist

Anthony
2D Artist
@@ -456,3 +631,5 @@ Project Lead, Lead Programmer
::Additional Assistance::
Mooky, Perfect Chaos, Dakota, Bardock, DethAyngel, Ebola, Badhead, $onik, Gigatron, Timex & Nat. +

+
diff --git a/build.bat b/build.bat index c30d971..86e143b 100644 --- a/build.bat +++ b/build.bat @@ -115,6 +115,29 @@ echo. echo. ) +if not "%1" == "" ( + if not exist "%1" ( + echo. + echo ************************************* + echo ERROR: "%1 doesn't exist" + echo ************************************* + echo. + goto :quit + ) + if exist pak9.pk3 ( + echo. + move pak9.pk3 "%1" + echo pak9.pk3 has been moved successfully! + echo. + ) else ( + echo. + echo ************************************* + echo ERROR: pak9.pk3 doesn't exist + echo ************************************* + echo. + ) +) + :quit set ERROR_CGAME_QVM= set ERROR_QAGAME_QVM= diff --git a/cfgs/bfp.cfg b/cfgs/bfp.cfg index 79c51e8..2a166d5 100644 --- a/cfgs/bfp.cfg +++ b/cfgs/bfp.cfg @@ -1,4 +1,4 @@ -c model bfp3-gokussj5/default +c model bfp2-tetsedah/default b TAB "+scores" b ENTER "+button2" b ESCAPE "togglemenu" @@ -7,7 +7,7 @@ b + "sizeup" b - "sizedown" b / "weapnext" b 0 "weapon 10" -b 1 "weapon 1bind 2 weapon 2bind 3 weapon 3bind 4 weapon 4bind 5 weapon 5" +b 1 "weapon 1" b 2 "weapon 2" b 3 "weapon 3" b 4 "weapon 4" @@ -17,19 +17,21 @@ b 7 "weapon 7" b 8 "weapon 8" b 9 "weapon 9" b = "sizeup" +b F "+button12" b [ "weapprev" b \ "+mlook" b ] "weapnext" b _ "sizedown" b ` "toggleconsole" b a "+moveleft" -b b "vstr piccolo" b c "+movedown" b d "+moveright" b f "+button12" -b m "vstr ginu" -b n "vstr goku" -b o "kiusetoggle" +b j "+button3" +b k "set_ki_charge" +b l "kiusetoggle" +b q "+button8" +b r "map_restart 0" b s "+back" b t "messagemode" b w "+forward" @@ -49,15 +51,7 @@ b END "centerview" b F1 "vote yes" b F2 "vote no" b F3 "ui_teamorders" -b F4 "vstr vegeta" -b F5 "vstr beb" -b F6 "vstr beb" -b F7 "vstr gotenks" -b F8 "vstr trunks" -b F9 "vstr freeza1" -b F10 "vstr cell" b F11 "screenshot" -b F12 "vstr freeza1" b MOUSE1 "+attack" b MOUSE2 "+button9" b MOUSE3 "+zoom" @@ -66,27 +60,27 @@ b MWHEELUP "weapprev" c cg_thirdPerson 1 c cg_thirdPersonRange 110 c cg_thirdPersonHeight -60 -c cg_fixedThirdPerson 1 +c cg_fixedThirdPerson 0 c cg_lightAuras 1 c cg_lightExplosions 1 c cg_lightweightAuras 0 -c cg_transformationAura 1 -c cg_simpleHUD 0 +c cg_transformationAura 0 +c cg_simpleHUD 1 c cg_drawOwnModel 0 -c cg_polygonAura 1 +c cg_polygonAura 0 c cg_spriteAura 0 -c cg_highPolyAura 1 -c cg_smallOwnAura 0 +c cg_highPolyAura 0 +c cg_smallOwnAura 1 c cg_permaglowUltimate 1 c cg_superdeformed 0 c cg_flytilt 1 -c cg_kiTrail 50 -c cg_beamTrail 25 +c cg_kiTrail 99 +c cg_beamTrail 81 c cg_bigExplosions 1 c cg_chargeupAlert 1 c cg_stfu 0 -c cg_lifekills 109 -c cg_lifedeaths 153 +c cg_lifekills 51 +c cg_lifedeaths 469 c cg_drawKiWarning 1 c sv_pure 1 c cg_explosionShell 1 @@ -96,7 +90,7 @@ c cg_crosshairColor 7 c cg_stableCrosshair 0 c cg_explosionRing 1 c cg_lowpolysphere 0 -c cg_musicUnpacked 1 +c cg_musicUnpacked 0 c cg_playHitSound 0 -c g_hitStun 1 +c g_hitStun 0 bfp diff --git a/cfgs/bfp_attacksets.cfg b/cfgs/bfp_attacksets.cfg index 956b0d9..7ce4f4b 100644 --- a/cfgs/bfp_attacksets.cfg +++ b/cfgs/bfp_attacksets.cfg @@ -1,11 +1,11 @@ attackset 1 attack 0 21 -attack 1 25 -attack 2 24 -attack 3 28 -attack 4 96 -modelPrefix kr- -defaultModel kr-gotenks +attack 1 16 +attack 2 15 +attack 3 23 +attack 4 13 +modelPrefix bfp1- +defaultModel bfp1-kyah attackset 2 attack 0 21 @@ -52,517 +52,4 @@ attack 4 17 modelPrefix bfp6- defaultModel bfp6-shilo -attackset 7 -attack 0 21 -attack 1 24 -attack 2 19 -attack 3 12 -attack 4 11 -modelPrefix gt- -defaultModel gt-pan - -attackset 8 -attack 0 21 -attack 1 27 -attack 2 15 -attack 3 28 -attack 4 52 -modelPrefix bfp33- -defaultmodel bfp33-chousu - -attackset 9 -attack 0 21 -attack 1 20 -attack 2 44 -attack 3 11 -attack 4 31 -modelPrefix genki -defaultModel genki - -attackset 10 -attack 0 21 -attack 1 14 -attack 2 25 -attack 3 101 -attack 4 26 -modelPrefix popo -defaultModel popo - -attackset 11 -attack 0 21 -attack 1 14 -attack 2 25 -attack 3 18 -attack 4 26 -modelPrefix kriccolo -defaultModel kriccolo - -attackset 12 -attack 0 21 -attack 1 57 -attack 2 95 -attack 3 79 -attack 4 86 -modelPrefix bfp44- -defaultModel bfp44-broly - -attackset 13 -attack 0 21 -attack 1 25 -attack 2 18 -attack 3 11 -attack 4 19 -modelPrefix bfp50- -defaultModel bfp50-puipui - -attackset 14 -attack 0 21 -attack 1 27 -attack 2 10 -attack 3 20 -attack 4 22 -modelPrefix yajirobe -defaultModel yajirobe - -attackset 15 -attack 0 21 -attack 1 25 -attack 2 24 -attack 3 28 -attack 4 107 -modelPrefix bfp24- -defaultModel bfp24-gohangsm - -attackset 16 -attack 0 21 -attack 1 16 -attack 2 15 -attack 3 23 -attack 4 13 -modelPrefix bfp12- -defaultModel bfp12-freeza - -attackset 17 -attack 0 21 -attack 1 27 -attack 2 20 -attack 3 14 -attack 4 11 -modelPrefix gurdo -defaultModel gurdo - -attackset 18 -attack 0 21 -attack 1 56 -attack 2 65 -attack 3 79 -attack 4 95 -modelPrefix bfp58- -defaultModel bfp58-chibgoku - -attackset 19 -attack 0 21 -attack 1 25 -attack 2 24 -attack 3 28 -attack 4 26 -modelPrefix paikuhan -defaultModel paikuhan - -attackset 20 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 11 -attack 4 95 -modelPrefix ussj -defaultModel ussj - -attackset 21 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 11 -attack 4 17 -modelPrefix vegeta -defaultModel vegeta - -attackset 22 -attack 0 21 -attack 1 20 -attack 2 24 -attack 3 23 -attack 4 19 -modelPrefix bfp5- -defaultmodel bfp5-vegetto - -attackset 23 -attack 0 16 -attack 1 14 -attack 2 14 -attack 3 15 -attack 4 26 -modelPrefix a19 -defaultModel a19 - -attackset 24 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 19 -attack 4 18 -modelPrefix bfp45- -defaultmodel bfp45-gotenks - -attackset 25 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 11 -attack 4 99 -modelPrefix majinveg -defaultModel majinveg - -attackset 26 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 11 -attack 4 32 -modelPrefix bfp53- -defaultmodel bfp53-oozaru - -attackset 27 -attack 0 21 -attack 1 14 -attack 2 24 -attack 3 28 -attack 4 96 -modelPrefix srkaio -defaultModel srkaio - -attackset 28 -attack 0 21 -attack 1 25 -attack 2 30 -attack 3 24 -attack 4 46 -modelPrefix bfp41- -defaultModel bfp41-dende - -attackset 29 -attack 0 21 -attack 1 20 -attack 2 24 -attack 3 11 -attack 4 13 -modelPrefix bfp11- -defaultModel bfp11-buu - -attackset 30 -attack 0 21 -attack 1 20 -attack 2 24 -attack 3 18 -attack 4 19 -modelPrefix yamcha -defaultModel yamcha - -attackset 31 -attack 0 10 -attack 1 25 -attack 2 24 -attack 3 28 -attack 4 19 -modelPrefix cyrax -defaultModel cyrax - -attackset 32 -attack 0 21 -attack 1 16 -attack 2 15 -attack 3 23 -attack 4 13 -modelPrefix bfp29- -defaultModel bfp29-Koola - -attackset 33 -attack 0 21 -attack 1 44 -attack 2 28 -attack 3 44 -attack 4 32 -modelPrefix bfp46- -defaultModel bfp46-Ginger - -atackset 34 -attack 0 21 -attack 1 10 -attack 2 15 -attack 3 24 -attack 4 109 -modelPrefix android16 -defaultmodel android16 - -attackset 35 -attack 0 21 -attack 1 14 -attack 2 15 -attack 3 18 -attack 4 22 -modelPrefix android17 -defaultModel android17 - -attackset 36 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 12 -attack 4 99 -modelPrefix bfp8- -defaultModel bfp8-broly - -attackset 37 -attack 0 55 -attack 1 27 -attack 2 47 -attack 3 24 -attack 4 63 -modelPrefix bfp56- -defaultModel bfp56-Tien - -attackset 38 -attack 0 28 -attack 1 24 -attack 2 19 -attack 3 13 -attack 4 17 -modelPrefix bfp7- -defaultModel bfp7-vegota - -attackset 39 -attack 0 21 -attack 1 14 -attack 2 24 -attack 3 12 -attack 4 26 -modelPrefix bfp49- -defaultModel bfp49-dragon - -attackset 40 -attack 0 21 -attack 1 18 -attack 2 24 -attack 3 11 -attack 4 40 -modelPrefix bfp9- -defaultmodel bfp9-gogeta - -attackset 41 -attack 0 54 -attack 1 95 -attack 2 24 -attack 3 11 -attack 4 32 -modelPrefix bfp100- -defaultmodel bfp100-vegetahell - -attackset 42 -attack 0 21 -attack 1 25 -attack 2 24 -attack 3 28 -attack 4 19 -modelPrefix notinuse -defaultModel notinuse - -attackset 43 -attack 0 14 -attack 1 35 -attack 2 25 -attack 3 12 -attack 4 19 -modelPrefix bfp13- -defaultModel bfp13-gokulo - -attackset 44 -attack 0 21 -attack 1 25 -attack 2 61 -attack 3 28 -attack 4 13 -modelPrefix bfp57- -defaultModel bfp57-fatbuu - -attackset 45 -attack 0 21 -attack 1 25 -attack 2 24 -attack 3 17 -attack 4 19 -modelPrefix bfp14- -defaultModel bfp14-yamhan - -attackset 46 -attack 0 25 -attack 1 24 -attack 2 17 -attack 3 99 -attack 4 26 -modelPrefix bfp16-veccolo -defaultModel bfp16-veccolo - -attackset 47 -attack 0 21 -attack 1 27 -attack 2 45 -attack 3 79 -attack 4 32 -modelPrefix bfp34- -defaultModel bfp34-cell - -attackset 48 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 19 -attack 4 32 -modelPrefix bfp26- -defaultModel bfp26-recoom2 - -attackset 49 -attack 0 18 -attack 1 24 -attack 2 28 -attack 3 19 -attack 4 31 -modelPrefix bfp19- -defaultModel bfp19-gokhun - -attackset 50 -attack 0 21 -attack 1 20 -attack 2 24 -attack 3 50 -attack 4 13 -modelPrefix bfp40- -defaultModel bfp40-kai - -attackset 51 -attack 0 21 -attack 1 12 -attack 2 24 -attack 3 11 -attack 4 19 -modelPrefix bfp20- -defaultModel bfp20-ssj2gohan - -attackset 52 -attack 0 21 -attack 1 14 -attack 2 24 -attack 3 58 -attack 4 42 -modelPrefix bfp52- -defaultModel bfp52-drgero - -attackset 53 -attack 0 21 -attack 1 25 -attack 2 24 -attack 3 19 -attack 4 37 -modelPrefix ussj- -defaultModel ussj-goku - -attackset 54 -attack 0 21 -attack 1 10 -attack 2 24 -attack 3 11 -attack 4 13 -modelPrefix bfp38- -defaultModel bfp38-zarbon - -attackset 55 -attack 0 21 -attack 1 16 -attack 2 24 -attack 3 12 -attack 4 17 -modelPrefix jeice -defaultModel jeice - -attackset 56 -attack 0 21 -attack 1 14 -attack 2 15 -attack 3 54 -attack 4 32 -modelPrefix bfp42- -defaultModel bfp42-nappa - -attackset 57 -attack 0 21 -attack 1 25 -attack 2 24 -attack 3 28 -attack 4 19 -modelPrefix bfp30- -defaultModel bfp30-gohan - -attackset 58 -attack 0 21 -attack 1 25 -attack 2 24 -attack 3 11 -attack 4 13 -modelPrefix bfp54- -defaultModel bfp54-kiwi - -attackset 59 -attack 0 44 -attack 1 44 -attack 2 44 -attack 3 44 -attack 4 77 -modelPrefix bfp27- -defaultModel bfp27-videl - -attackset 60 -attack 0 21 -attack 1 10 -attack 2 94 -attack 3 32 -attack 4 22 -modelPrefix bfp36- -defaultModel bfp36-celljr - -attackset 61 -attack 0 21 -attack 1 24 -attack 2 49 -attack 3 48 -attack 4 32 -modelPrefix bfp35- -defaultModel bfp35-berta - -attackset 62 -attack 0 21 -attack 1 10 -attack 2 44 -attack 3 31 -attack 4 32 -modelPrefix bfp55- -defaultModel bfp55-Yarkan - -attackset 63 -attack 0 21 -attack 1 44 -attack 2 28 -attack 3 44 -attack 4 32 -modelPrefix bfp46- -defaultModel bfp46-Ginger - end \ No newline at end of file diff --git a/cfgs/bfp_weapon.cfg b/cfgs/bfp_weapon.cfg index d3407bb..461411a 100644 --- a/cfgs/bfp_weapon.cfg +++ b/cfgs/bfp_weapon.cfg @@ -1,7 +1,7 @@ (large_ki_blast) weaponNum 10 attackType missile -weaponTime 750 +weaponTime 1250 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 @@ -10,8 +10,8 @@ chargeAttack 0 chargeAutoFire 0 minCharge 0 maxCharge 0 -damage 30 -splashDamage 30 +damage 40 +splashDamage 40 chargeDamageMult 0 maxDamage 0 radius 40 @@ -20,15 +20,18 @@ chargeRadiusMult 0 chargeExpRadiusMult 0 maxRadius 0 maxExpRadius 0 -missileSpeed 6000 +missileSpeed 4000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -36,6 +39,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 20 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (impact_beam) weaponNum 11 @@ -47,11 +56,11 @@ kiPct 0 kiCost 250 chargeAttack 1 chargeAutoFire 0 -minCharge 2 +minCharge 1 maxCharge 6 -damage 10 -splashDamage 10 -chargeDamageMult 10 +damage 5 +splashDamage 5 +chargeDamageMult 5 maxDamage 50 radius 30 explosionRadius 350 @@ -62,12 +71,15 @@ maxExpRadius 0 missileSpeed 2000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 1 @@ -75,6 +87,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (super_homing) weaponNum 12 @@ -101,12 +119,15 @@ maxExpRadius 0 missileSpeed 4000 homing 0.5 homingRange 2000 +homingAcceleration 0 range 0 loopingAnim 1 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -114,38 +135,47 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (deathball) weaponNum 13 attackType missile -weaponTime 1500 +weaponTime 750 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 -kiCost 750 +kiCost 250 chargeAttack 1 chargeAutoFire 0 minCharge 2 maxCharge 6 -damage 80 -splashDamage 30 -chargeDamageMult 10 -maxDamage 0 -radius 75 -explosionRadius 400 -chargeRadiusMult 25 -chargeExpRadiusMult 150 -maxRadius 200 +damage 20 +splashDamage 20 +chargeDamageMult 20 +maxDamage 100 +radius 130 +explosionRadius 800 +chargeRadiusMult 30 +chargeExpRadiusMult 300 +maxRadius 0 maxExpRadius 0 -missileSpeed 5000 +missileSpeed 3000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 1 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 1 @@ -153,6 +183,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 100 +missileAcceleration 0 +multishot 0 +bounces 1 +bounceFriction 0.7 +missileDuration 2000 (eyebeam) weaponNum 14 @@ -179,12 +215,15 @@ maxExpRadius 0 missileSpeed 0 homing 0 homingRange 0 -range 2500 +homingAcceleration 0 +range 2200 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -192,6 +231,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 0 (finger_beam) weaponNum 15 @@ -218,12 +263,15 @@ maxExpRadius 0 missileSpeed 0 homing 0 homingRange 0 +homingAcceleration 0 range 8192 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -231,6 +279,12 @@ blinding 0 extraKnockback 0 railTrail 1 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 0 (finger_blast) weaponNum 16 @@ -238,7 +292,7 @@ attackType hitscan weaponTime 50 randomWeaponTime 100 kiCostAsPct 1 -kiPct .010 +kiPct .007 kiCost 0 chargeAttack 0 chargeAutoFire 0 @@ -257,12 +311,15 @@ maxExpRadius 0 missileSpeed 0 homing 0 homingRange 0 -range 1500 +homingAcceleration 0 +range 2200 loopingAnim 1 noAttackAnim 0 alternatingXOffset 0 -randYOffset 35 -randXOffset 35 +randYOffset 0 +randXOffset 0 +coneOfFireX 50 +coneOfFireY 50 piercing 0 reflective 0 priority 0 @@ -270,6 +327,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 0 (ultimate_blast) weaponNum 17 @@ -283,9 +346,9 @@ chargeAttack 1 chargeAutoFire 0 minCharge 2 maxCharge 6 -damage 10 -splashDamage 10 -chargeDamageMult 30 +damage 20 +splashDamage 20 +chargeDamageMult 20 maxDamage 100 radius 50 explosionRadius 100 @@ -296,12 +359,15 @@ maxExpRadius 700 missileSpeed 2500 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 1 @@ -309,11 +375,17 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (homing_special) weaponNum 18 attackType rdmissile -weaponTime 1250 +weaponTime 1000 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 @@ -335,12 +407,15 @@ maxExpRadius 0 missileSpeed 2000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -349,6 +424,12 @@ extraKnockback 0 railTrail 0 movementPenalty 0 explosionSpawn 9 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (homing_special_spawn) weaponNum 9 @@ -372,15 +453,18 @@ chargeRadiusMult 0 chargeExpRadiusMult 50 maxRadius 0 maxExpRadius 250 -missileSpeed 700 +missileSpeed 1000 homing 0.9 homingRange 800 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -388,6 +472,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (power_wave_blast) weaponNum 19 @@ -396,13 +486,13 @@ weaponTime 1200 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 -kiCost 600 +kiCost 500 chargeAttack 1 -minCharge 4 +minCharge 3 maxCharge 6 -damage 25 -splashDamage 25 -chargeDamageMult 25 +damage 60 +splashDamage 60 +chargeDamageMult 10 maxDamage 0 radius 25 explosionRadius 100 @@ -413,12 +503,15 @@ maxExpRadius 0 missileSpeed 2500 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 1 @@ -426,6 +519,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (tornado_blast) weaponNum 20 @@ -433,7 +532,7 @@ attackType hitscan weaponTime 100 randomWeaponTime 0 kiCostAsPct 1 -kiPct .010 +kiPct .015 kiCost 0 chargeAttack 0 chargeAutoFire 0 @@ -452,12 +551,15 @@ maxExpRadius 0 missileSpeed 0 homing 0 homingRange 0 -range 500 +homingAcceleration 0 +range 450 loopingAnim 1 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 1 reflective 1 priority 0 @@ -465,6 +567,12 @@ blinding 0 extraKnockback 200 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 0 (ki_blast) weaponNum 21 @@ -488,15 +596,18 @@ chargeRadiusMult 0 chargeExpRadiusMult 0 maxRadius 0 maxExpRadius 0 -missileSpeed 6000 +missileSpeed 5000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -504,20 +615,26 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0.9 +missileDuration 10000 (razor_disk) weaponNum 22 attackType missile -weaponTime 1000 +weaponTime 1250 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 -kiCost 250 +kiCost 500 chargeAttack 1 chargeAutoFire 0 minCharge 2 maxCharge 6 -damage 300 +damage 100 splashDamage 0 chargeDamageMult 0 maxDamage 0 @@ -527,15 +644,18 @@ chargeRadiusMult 25 chargeExpRadiusMult 0 maxRadius 0 maxExpRadius 0 -missileSpeed 2500 +missileSpeed 4000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 1 reflective 0 priority 3 @@ -543,6 +663,12 @@ blinding 0 extraKnockback -1000 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (homing_razor_disk) weaponNum 23 @@ -554,10 +680,10 @@ kiPct 0 kiCost 250 chargeAttack 1 chargeAutoFire 0 -minCharge 2 -maxCharge 6 -damage 20 -splashDamage 20 +minCharge 1 +maxCharge 1 +damage 35 +splashDamage 35 chargeDamageMult 0 maxDamage 0 radius 75 @@ -569,12 +695,15 @@ maxExpRadius 0 missileSpeed 1000 homing 0.5 homingRange 2000 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 1 reflective 0 priority 3 @@ -582,6 +711,12 @@ blinding 0 extraKnockback -1000 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (ki_storm) weaponNum 24 @@ -589,16 +724,16 @@ attackType missile weaponTime 200 randomWeaponTime 0 kiCostAsPct 1 -kiPct .025 +kiPct .020 kiCost 0 chargeAttack 0 chargeAutoFire 0 minCharge 0 maxCharge 0 -damage 15 -splashDamage 15 +damage 10 +splashDamage 10 chargeDamageMult 0 -maxDamage 15 +maxDamage 0 radius 15 explosionRadius 100 chargeRadiusMult 0 @@ -608,19 +743,28 @@ maxExpRadius 0 missileSpeed 8000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 1 noAttackAnim 0 alternatingXOffset 15 randYOffset 25 randXOffset 7 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 blinding 0 -extraKnockback 0 +extraKnockback -25 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (homingball) weaponNum 25 @@ -632,10 +776,10 @@ kiPct 0 kiCost 100 chargeAttack 1 chargeAutoFire 0 -minCharge 2 -maxCharge 2 -damage 30 -splashDamage 30 +minCharge 1 +maxCharge 1 +damage 40 +splashDamage 40 chargeDamageMult 0 maxDamage 0 radius 20 @@ -644,15 +788,18 @@ chargeRadiusMult 0 chargeExpRadiusMult 0 maxRadius 0 maxExpRadius 0 -missileSpeed 700 +missileSpeed 800 homing 0.3 -homingRange 2000 +homingRange 2500 +homingAcceleration 0 range 0 loopingAnim 1 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -660,6 +807,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (corkscrew_blast) weaponNum 26 @@ -673,25 +826,28 @@ chargeAttack 1 chargeAutoFire 0 minCharge 2 maxCharge 6 -damage 25 +damage 35 splashDamage 0 -chargeDamageMult 25 +chargeDamageMult 15 maxDamage 100 radius 10 explosionRadius 250 chargeRadiusMult 0 -chargeExpRadiusMult 25 +chargeExpRadiusMult 0 maxRadius 0 maxExpRadius 0 missileSpeed 3000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 1 @@ -699,6 +855,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (blinding_flash) weaponNum 27 @@ -710,13 +872,13 @@ kiPct 0 kiCost 100 chargeAttack 1 chargeAutoFire 0 -minCharge 3 -maxCharge 3 +minCharge 2 +maxCharge 2 damage 1 splashDamage 0 chargeDamageMult 0 maxDamage 0 -radius 800 +radius 1200 explosionRadius 0 chargeRadiusMult 0 chargeExpRadiusMult 0 @@ -725,12 +887,15 @@ maxExpRadius 0 missileSpeed 0 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 1 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 0 @@ -738,11 +903,17 @@ blinding 1 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 0 (mantis_blast) weaponNum 28 attackType beam -weaponTime 500 +weaponTime 650 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 @@ -751,220 +922,28 @@ chargeAttack 1 chargeAutoFire 0 minCharge 3 maxCharge 6 -damage 10 -splashDamage 10 -chargeDamageMult 15 -maxDamage 75 -radius 0 -explosionRadius 0 -chargeRadiusMult 15 -chargeExpRadiusMult 120 -maxRadius 45 -maxExpRadius 500 -missileSpeed 2000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(mouthbeam) -weaponNum 29 -attackType sbeam -weaponTime 250 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 250 -chargeAttack 0 -chargeAutoFire 0 -minCharge 0 -maxCharge 0 damage 15 splashDamage 15 -chargeDamageMult 0 -maxDamage 15 -radius 50 -explosionRadius 200 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 1000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(Force Field) -weaponNum 30 -attackType forcefield -weaponTime 200 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 90 -chargeAttack 1 -chargeAutoFire 1 -minCharge 3 -maxCharge 0 -damage 2 -splashDamage 2 -chargeDamageMult 0 -maxDamage 2 -radius 800 -explosionRadius 0 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 1 -priority 2 -blinding 0 -extraKnockback 1 -railTrail 0 -movementPenalty 0 - -(kameso_blast) -weaponNum 31 -attackType beam -weaponTime 1500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 1000 -chargeAttack 1 -chargeAutoFire 0 -minCharge 2 -maxCharge 6 -damage 80 -splashDamage 10 -chargeDamageMult 30 -maxDamage 100 -radius 50 -explosionRadius 250 -chargeRadiusMult 25 -chargeExpRadiusMult 200 -maxRadius 0 -maxExpRadius 750 -missileSpeed 2500 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(homing_beam) -weaponNum 32 -attackType beam -weaponTime 1500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 600 -chargeAttack 1 -chargeAutoFire 0 -minCharge 2 -maxCharge 6 -damage 10 -splashDamage 10 chargeDamageMult 10 -maxDamage 50 -radius 30 -explosionRadius 350 -chargeRadiusMult 0 -chargeExpRadiusMult 200 -maxRadius 0 -maxExpRadius 0 -missileSpeed 800 -homing 0.3 -homingRange 2000 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(sky_blast) -weaponNum 35 -attackType beam -weaponTime 500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 250 -chargeAttack 1 -chargeAutoFire 0 -minCharge 3 -maxCharge 6 -damage 20 -splashDamage 10 -chargeDamageMult 15 -maxDamage 75 +maxDamage 0 radius 0 -explosionRadius 6000 +explosionRadius 0 chargeRadiusMult 15 chargeExpRadiusMult 120 maxRadius 45 -maxExpRadius 10000 +maxExpRadius 500 missileSpeed 2000 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 +coneOfFireX 0 +coneOfFireY 0 piercing 0 reflective 0 priority 1 @@ -972,83 +951,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 - -(genki) -weaponNum 37 -attackType missile -weaponTime 4000 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 100 -chargeAttack 1 -chargeAutoFire 0 -minCharge 2 -maxCharge 0 -damage 60 -splashDamage 60 -chargeDamageMult 20 -maxDamage 0 -radius 25 -explosionRadius 400 -chargeRadiusMult 90 -chargeExpRadiusMult 200 -maxRadius 200 -maxExpRadius 0 -missileSpeed 900 -homing 0 -homingRange 0 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(final_kamehameha) -weaponNum 40 -attackType beam -weaponTime 1200 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 600 -chargeAttack 1 -minCharge 4 -maxCharge 6 -damage 70 -splashDamage 0 -chargeDamageMult 0 -maxDamage 0 -radius 20 -explosionRadius 7000 -chargeRadiusMult 60 -chargeExpRadiusMult 1200 -maxRadius 500 -maxExpRadius 12000 -missileSpeed 3000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (aga) weaponNum 99 @@ -1057,16 +965,16 @@ weaponTime 200 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 -kiCost 100 +kiCost 200 chargeAttack 1 chargeAutoFire 1 minCharge 4 maxCharge 0 -damage 1 +damage 20 splashDamage 0 chargeDamageMult 0 maxDamage 0 -radius 8000 +radius 900 explosionRadius 0 chargeRadiusMult 0 chargeExpRadiusMult 0 @@ -1075,138 +983,27 @@ maxExpRadius 0 missileSpeed 0 homing 0 homingRange 0 +homingAcceleration 0 range 0 loopingAnim 1 noAttackAnim 0 alternatingXOffset 0 randYOffset 0 randXOffset 0 -piercing 0 -reflective 1 -priority 2 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 2 - -(Weak_beam_rage) -weaponNum 61 -attackType missile -weaponTime 400 -randomWeaponTime 0 -kiCostAsPct 1 -kiPct .025 -kiCost 0 -chargeAttack 0 -chargeAutoFire 0 -minCharge 0 -maxCharge 0 -damage 8 -splashDamage 5 -chargeDamageMult 0 -maxDamage 10 -radius 50 -explosionRadius 100 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 100 -missileSpeed 3000 -homing 0.3 -homingRange 4000 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 50 -randYOffset 40 -randXOffset 50 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 25 -railTrail 0 -movementPenalty 0 - -(ghost) -weaponNum 46 -attackType rdmissile -weaponTime 1500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 400 -chargeAttack 1 -chargeAutoFire 0 -minCharge 3 -maxCharge 6 -damage 55 -splashDamage 55 -chargeDamageMult 0 -maxDamage 0 -radius 30 -explosionRadius 120 -chargeRadiusMult 0 -chargeExpRadiusMult 40 -maxRadius 0 -maxExpRadius 0 -missileSpeed 2000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 coneOfFireX 0 coneOfFireY 0 piercing 0 -reflective 0 -priority 0 +reflective 1 +priority 2 blinding 0 extraKnockback 0 railTrail 0 -movementPenalty 0 -explosionSpawn 9 - -(beam_rage) -weaponNum 109 -attackType missile -weaponTime 400 -randomWeaponTime 0 -kiCostAsPct 1 -kiPct .075 -kiCost 0 -chargeAttack 0 -chargeAutoFire 0 -minCharge 0 -maxCharge 0 -damage 15 -splashDamage 5 -chargeDamageMult 0 -maxDamage 15 -radius 50 -explosionRadius 100 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 100 -missileSpeed 3000 -homing 0.9 -homingRange 4000 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 50 -randYOffset 40 -randXOffset 50 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 25 -railTrail 0 movementPenalty 2 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 0 end diff --git a/cfgs/bfp_weapon2.cfg b/cfgs/bfp_weapon2.cfg index 582f2a3..f7723e6 100644 --- a/cfgs/bfp_weapon2.cfg +++ b/cfgs/bfp_weapon2.cfg @@ -1,635 +1,21 @@ -(Absorb) -weaponNum 42 -attackType hitscan -weaponTime 100 -randomWeaponTime 0 -kiCostAsPct 1 -kiPct -20 -kiCost 0 -chargeAttack 0 -chargeAutoFire 0 -minCharge 0 -maxCharge 0 -damage 8 -splashDamage 0 -chargeDamageMult 0 -maxDamage 0 -radius 220 -explosionRadius 0 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 600 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 1 -reflective 1 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(Kaioken) -weaponNum 44 -attackType forcefield -weaponTime 850 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 60 -chargeAttack 1 -chargeAutoFire 0 -minCharge 3 -maxCharge 0 -damage 70 -splashDamage 70 -chargeDamageMult 2 -maxDamage 80 -radius 140 -explosionRadius 0 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 1 -priority 2 -blinding 0 -extraKnockback 200 -railTrail 0 -movementPenalty 0 - -(WEAK BEAM TIER 3) (Krillin and Cell Jr) -weaponNum 45 -attackType beam -weaponTime 1200 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 500 -chargeAttack 1 -minCharge 3 -maxCharge 6 -damage 10 -splashDamage 5 -chargeDamageMult 15 -maxDamage 55 -radius 25 -explosionRadius 100 -chargeRadiusMult 25 -chargeExpRadiusMult 120 -maxRadius 75 -maxExpRadius 0 -missileSpeed 2500 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(Force Tornado) -weaponNum 49 -attackType forcefield -weaponTime 200 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 100 -chargeAttack 1 -chargeAutoFire 1 -minCharge 3 -maxCharge 0 -damage 10 -splashDamage 10 -chargeDamageMult 0 -maxDamage 10 -radius 800 -explosionRadius 0 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 1 -priority 2 -blinding 0 -extraKnockback 100 -railTrail 0 -movementPenalty 2 - -(Bomb) -weaponNum 54 -attackType missile -weaponTime 1000 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 350 -chargeAttack 1 -chargeAutoFire 0 -minCharge 3 -maxCharge 6 -damage 55 -splashDamage 55 -chargeDamageMult 0 -maxDamage 55 -radius 20 -explosionRadius 650 -chargeRadiusMult 0 -chargeExpRadiusMult 650 -maxRadius 0 -maxExpRadius 0 -missileSpeed 1000 -homing 0 -homingRange 0 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(weak ass finger_beam) -weaponNum 55 -attackType hitscan -weaponTime 1500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 200 -chargeAttack 0 -chargeAutoFire 0 -minCharge 0 -maxCharge 0 -damage 20 -splashDamage 10 -chargeDamageMult 0 -maxDamage 0 -radius 1 -explosionRadius 200 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 20000 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 1 -movementPenalty 0 - -(Tiens weak homing_special) -weaponNum 47 -attackType rdmissile -weaponTime 1250 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 150 -chargeAttack 1 -chargeAutoFire 0 -minCharge 1 -maxCharge 6 -damage 25 -splashDamage 15 -chargeDamageMult 0 -maxDamage 0 -radius 30 -explosionRadius 120 -chargeRadiusMult 0 -chargeExpRadiusMult 40 -maxRadius 0 -maxExpRadius 0 -missileSpeed 2000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 -explosionSpawn 9 - -(Fire Crusher Ball) -weaponNum 48 -attackType missile -weaponTime 2000 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 250 -chargeAttack 1 -chargeAutoFire 0 -minCharge 3 -maxCharge 6 -damage 30 -splashDamage 30 -chargeDamageMult 5 -maxDamage 0 -radius 30 -explosionRadius 250 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 4000 -homing 0.5 -homingRange 4000 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(Surpreme Kai) -weaponNum 50 -attackType missile -weaponTime 1700 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 250 -chargeAttack 1 -chargeAutoFire 0 -minCharge 3 -maxCharge 6 -damage 30 -splashDamage 30 -chargeDamageMult 5 -maxDamage 0 -radius 30 -explosionRadius 250 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 4000 -homing 0.5 -homingRange 4000 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(Timed-Self-destuct) -weaponNum 52 -attackType rdmissile -weaponTime 1250 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 200 -chargeAttack 1 -chargeAutoFire 0 -minCharge 1 -maxCharge 6 -damage 15 -splashDamage 10 -chargeDamageMult 40 -maxDamage 80 -radius 30 -explosionRadius 120 -chargeRadiusMult 0 -chargeExpRadiusMult 40 -maxRadius 0 -maxExpRadius 0 -missileSpeed 850 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 -explosionSpawn 53 - -(Ssj4 goku's Cone Attack) -weaponNum 65 -attackType hitscan -weaponTime 100 -randomWeaponTime 0 -kiCostAsPct 1 -kiPct 0.010 -kiCost 0 -chargeAttack 0 -chargeAutoFire 0 -minCharge 0 -maxCharge 0 -damage 9 -splashDamage 0 -chargeDamageMult 0 -maxDamage 0 -radius 200 -explosionRadius 0 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 200 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 1 -priority 0 -blinding 0 -extraKnockback 200 -railTrail 0 -movementPenalty 0 - -(Double beamer {quick bba missile}) -weaponNum 57 -attackType missile -weaponTime 800 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 200 -chargeAttack 1 -chargeAutoFire 0 -minCharge 2 -maxCharge 6 -damage 30 -splashDamage 30 -chargeDamageMult 10 -maxDamage 0 -radius 75 -explosionRadius 400 -chargeRadiusMult 2 -chargeExpRadiusMult 2 -maxRadius 200 -maxExpRadius 0 -missileSpeed 4000 -homing 0.3 -homingRange 3000 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(blinding_flash_push) -weaponNum 56 -attackType forcefield -weaponTime 500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 100 -chargeAttack 1 -chargeAutoFire 0 -minCharge 2 -maxCharge 3 -damage 5 -splashDamage 0 -chargeDamageMult 0 -maxDamage 0 -radius 800 -explosionRadius 0 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 1 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 1 -extraKnockback 200 -railTrail 0 -movementPenalty 1 - -(long light kame) -weaponNum 58 -attackType beam -weaponTime 2000 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 250 -chargeAttack 1 -chargeAutoFire 0 -minCharge 4 -maxCharge 6 -damage 5 -splashDamage 5 -chargeDamageMult 5 -maxDamage 50 -radius 30 -explosionRadius 350 -chargeRadiusMult 0 -chargeExpRadiusMult 200 -maxRadius 0 -maxExpRadius 0 -missileSpeed 2000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(Tri Beam) -weaponNum 63 -attackType beam -weaponTime 500 -randomWeaponTime 0 -kiCostAsPct 200 -kiPct 0 -kiCost 400 -chargeAttack 1 -chargeAutoFire 0 -minCharge 0 -maxCharge 6 -damage 5 -splashDamage 5 -chargeDamageMult 14 -maxDamage 100 -radius 50 -explosionRadius 100 -chargeRadiusMult 25 -chargeExpRadiusMult 200 -maxRadius 0 -maxExpRadius 700 -missileSpeed 2500 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(retard ki_blast) -weaponNum 77 -attackType missile -weaponTime 1000 +(mouthbeam) +weaponNum 29 +attackType sbeam +weaponTime 250 randomWeaponTime 0 kiCostAsPct 0 kiPct 0 -kiCost 4000 +kiCost 250 chargeAttack 0 chargeAutoFire 0 minCharge 0 maxCharge 0 -damage 20 -splashDamage 20 +damage 15 +splashDamage 15 chargeDamageMult 0 -maxDamage 0 -radius 20 -explosionRadius 125 +maxDamage 15 +radius 50 +explosionRadius 200 chargeRadiusMult 0 chargeExpRadiusMult 0 maxRadius 0 @@ -637,190 +23,7 @@ maxExpRadius 0 missileSpeed 1000 homing 0 homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(dragon) -weaponNum 86 -attackType missile -weaponTime 1400 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 850 -chargeAttack 1 -chargeAutoFire 0 -minCharge 3 -maxCharge 6 -damage 300 -splashDamage 200 -chargeDamageMult 0 -maxDamage 200 -radius 20 -explosionRadius 250 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 800 -homing 0.4 -homingRange 6000 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(light kame) -weaponNum 94 -attackType beam -weaponTime 500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 250 -chargeAttack 1 -chargeAutoFire 0 -minCharge 4 -maxCharge 6 -damage 5 -splashDamage 5 -chargeDamageMult 5 -maxDamage 50 -radius 30 -explosionRadius 350 -chargeRadiusMult 0 -chargeExpRadiusMult 200 -maxRadius 0 -maxExpRadius 0 -missileSpeed 2000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 1 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - -(Random Explosions) -weaponNum 95 -attackType missile -weaponTime 20 -kiCost 60 -radius 10 -damage 22 -splashDamage 18 -missilespeed 8000 -explosionRadius 100 -reflective 0 -priority 0 -randYOffset 300 -randXOffset 300 -randzoffset 200 - -(ghost kamikaze) -weaponNum 96 -attackType missile -weaponTime 1400 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 120 -chargeAttack 1 -chargeAutoFire 0 -minCharge 4 -maxCharge 6 -damage 15 -splashDamage 20 -chargeDamageMult 5 -maxDamage 0 -radius 20 -explosionRadius 1 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 1000 -homing 1.0 -homingRange 4000 -homingAcceleration 1.4 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 500 -coneOfFireY 200 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 -missileGravity 0 -missileAcceleration 0.96 -multishot 4 -bounces 0 -bounceFriction 0 -missileDuration 10000 - -(STANDERED BEAM TIER 4) (Brolly-Gokssj3-Vegmajin ect...) -weaponNum 79 -attackType beam -weaponTime 1200 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 500 -chargeAttack 1 -minCharge 3 -maxCharge 6 -damage 15 -splashDamage 5 -chargeDamageMult 20 -maxDamage 80 -radius 25 -explosionRadius 100 -chargeRadiusMult 25 -chargeExpRadiusMult 120 -maxRadius 75 -maxExpRadius 0 -missileSpeed 2500 -homing 0 -homingRange 0 +homingAcceleration 0 range 0 loopingAnim 0 noAttackAnim 0 @@ -836,6 +39,12 @@ blinding 0 extraKnockback 0 railTrail 0 movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 0 +bounceFriction 0 +missileDuration 10000 (multiball) weaponNum 100 @@ -981,6 +190,54 @@ bounces 0 bounceFriction 0 missileDuration 3000 +(ki_storm_bounces) +weaponNum 103 +attackType missile +weaponTime 200 +randomWeaponTime 0 +kiCostAsPct 1 +kiPct .020 +kiCost 0 +chargeAttack 0 +chargeAutoFire 0 +minCharge 0 +maxCharge 0 +damage 25 +splashDamage 25 +chargeDamageMult 0 +maxDamage 0 +radius 15 +explosionRadius 100 +chargeRadiusMult 0 +chargeExpRadiusMult 0 +maxRadius 0 +maxExpRadius 0 +missileSpeed 8000 +homing 0 +homingRange 0 +homingAcceleration 0 +range 0 +loopingAnim 1 +noAttackAnim 0 +alternatingXOffset 15 +randYOffset 25 +randXOffset 7 +coneOfFireX 0 +coneOfFireY 0 +piercing 0 +reflective 0 +priority 0 +blinding 0 +extraKnockback -25 +railTrail 0 +movementPenalty 0 +missileGravity 0 +missileAcceleration 0 +multishot 0 +bounces 1 +bounceFriction 0.95 +missileDuration 3000 + (homingball_accel) weaponNum 104 attackType missile @@ -1078,128 +335,4 @@ bounceFriction 0.93 noZBounce 0.000001 missileDuration 2000 -(Timed-Self-destuct) -weaponNum 106 -attackType rdmissile -weaponTime 1250 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 200 -chargeAttack 1 -chargeAutoFire 0 -minCharge 1 -maxCharge 6 -damage 15 -splashDamage 10 -chargeDamageMult 40 -maxDamage 80 -radius 30 -explosionRadius 120 -chargeRadiusMult 0 -chargeExpRadiusMult 40 -maxRadius 0 -maxExpRadius 0 -missileSpeed 850 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 -explosionSpawn 53 - -(aga2) -weaponNum 107 -attackType forcefield -weaponTime 200 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 200 -chargeAttack 1 -chargeAutoFire 1 -minCharge 3 -maxCharge 0 -damage 20 -splashDamage 0 -chargeDamageMult 0 -maxDamage 0 -radius 900 -explosionRadius 0 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 0 -homing 0 -homingRange 0 -range 0 -loopingAnim 1 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 1 -priority 2 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 2 - -(Very LOOOONG BEAM) -weaponNum 108 -attackType beam -weaponTime 5500 -randomWeaponTime 0 -kiCostAsPct 0 -kiPct 0 -kiCost 500 -chargeAttack 1 -chargeAutoFire 0 -minCharge 4 -maxCharge 6 -damage 100 -splashDamage 80 -chargeDamageMult 5 -maxDamage 110 -radius 20 -explosionRadius 190 -chargeRadiusMult 0 -chargeExpRadiusMult 0 -maxRadius 0 -maxExpRadius 0 -missileSpeed 3000 -homing 0 -homingRange 0 -range 0 -loopingAnim 0 -noAttackAnim 0 -alternatingXOffset 0 -randYOffset 0 -randXOffset 0 -coneOfFireX 0 -coneOfFireY 0 -piercing 0 -reflective 0 -priority 0 -blinding 0 -extraKnockback 0 -railTrail 0 -movementPenalty 0 - end diff --git a/cfgs/default.cfg b/cfgs/default.cfg index d7ef398..05a9b26 100644 --- a/cfgs/default.cfg +++ b/cfgs/default.cfg @@ -1,44 +1,39 @@ -attackName 0 "Kikou" -attackName 1 "Sokidan" -attackName 2 "Renzoku Energy Dan" -attackName 3 "Masenko" -attackName 4 "Angry Gohan Attack" +attackName 0 "Ki Blast" +attackName 1 "Tornado Blast" +attackName 2 "Sky Seeker" +attackName 3 "Earth Seeker" +attackName 4 "Heaven's Wrath" attackIcon 0 "icons/kiblast" -attackIcon 1 "icons/lightningball" -attackIcon 2 "icons/lstorm_icon" -attackIcon 3 "icons/mantisblast" -attackIcon 4 "icons/aga" +attackIcon 1 "icons/tornadoblast" +attackIcon 2 "icons/redattack_icon" +attackIcon 3 "icons/earthseeker_icon" +attackIcon 4 "icons/powerwaveblast" missileSound 0 "sound/weapons/rocket/rockfly.wav" -missileSound 1 "sound/bfp/homingballfly.wav" -missileSound 2 "sound/weapons/rocket/rockfly.wav" +missileSound 2 "sound/bfp/homingballfly.wav" missileSound 3 "sound/weapons/rocket/rockfly.wav" +missileSound 4 "sound/weapons/rocket/rockfly.wav" flashSound 0 "sound/weapons/bfg/bfg_fire.wav" -flashSound 1 "sound/bfp/homingballfire.wav" -flashSound 2 "sound/weapons/bfg/bfg_fire.wav" -firingSound 3 "sound/weapons/rocket/rockfly.wav" +flashSound 1 "sound/weapons/bfg/bfg_fire.wav" +flashSound 2 "sound/bfp/homingballfire.wav" +flashSound 3 "sound/weapons/bfg/bfg_fire.wav" +chargeSound 2 "sound/bfp/attackcharge1.wav" +chargeSound 4 "sound/bfp/attackcharge1.wav" +firingSound 1 "sound/weapons/rocket/rockfly.wav" firingSound 4 "sound/weapons/rocket/rockfly.wav" -chargeSound 1 "sound/bfp/attackcharge1.wav" -chargeSound 3 "sound/bfp/attackcharge1.wav" - -attackFireVoice 0 "sound\player\bfp2-gohan_ssj\goh_ka.wav" -attackChargeVoice 1 0 "sound\player\bfp2-gohan_ssj\goh_whyu.wav" -attackFireVoice 2 "sound\player\bfp2-gohan_ssj\goh_ka.wav" -attackChargeVoice 3 1 "sound\player\bfp2-gohan_ssj\goh_daddy.wav" -attackFireVoice 3 "sound\player\bfp2-gohan_ssj\goh_masenko.wav" -attackFireVoice 4 "sound\player\bfp2-gohan_ssj\goh_screamki.wav" +constantFireAttack 1 1 attackTag 0 "tag_left" -attackTagPart 0 torso +attackTagPart 0 "torso" attackTag 1 "tag_right" -attackTagPart 1 torso +attackTagPart 1 "torso" attackTag 2 "tag_right" -attackTagPart 2 torso -attackTag 3 "tag_left" -attackTagPart 3 torso +attackTagPart 2 "torso" +attackTag 3 "tag_right" +attackTagPart 3 "torso" attackTag 4 "tag_right" -attackTagPart 4 torso +attackTagPart 4 "torso" missileShader 0 "KiBlastAttackShader" missileModel 0 "models/weaphits/kiblast.md3" @@ -48,34 +43,34 @@ missileDlight 0 200 missileDlightColor 0 1 0.75 0 missileModelRotation 0 0.2 -missileShader 1 "LightningBallAttackShader" -missileRadius 1 50 -missileRotation 1 15 -missileDlight 1 200 -missileDlightColor 1 1 0.5 0 -flashShader 1 "LightningBallChargeShader" -flashRadius 1 25 - -missileShader 2 "lightning_storm" -missileRadius 2 30 +missileShader 2 "redattack" +missileRadius 2 50 missileRotation 2 15 -missileTrailFunc 2 "rocket" missileDlight 2 200 -missileDlightColor 2 0 0 0.75 -missileModelRotation 2 0.2 +missileDlightColor 2 1 0 0.5 +flashShader 2 "redattack" +flashRadius 2 25 -missileShader 3 "MantisAttackShader" -beamShader 3 "MantisBeamShader" -missileRadius 3 75 +missileShader 3 "earth_seeker" +missileRadius 3 30 missileRotation 3 15 -missileTrailFunc 3 "beam" +missileTrailFunc 3 "rocket" missileDlight 3 200 -missileDlightColor 3 1 1 0.5 -flashShader 3 "MantisFlashShader" -flashRadius 3 15 -flashScaleFactor 3 2 -firingFlashRadius 3 30 -firingFlashScaleFactor 3 1.4 +missileDlightColor 3 1 0 0.3 +missileModelRotation 3 0.2 + +missileShader 4 "PowerWaveAttackShader" +beamShader 4 "PowerWaveBeamShader" +missileRadius 4 0 +missileRadiusChargeMult 4 75 +missileTrailFunc 4 "beam" +missileDlight 4 200 +missileDlightColor 4 0.18 0.77 1 +flashShader 4 "PowerWaveFlashShader" +flashRadius 4 20 +flashScaleFactor 4 0 +firingFlashRadius 4 50 +firingFlashScaleFactor 4 1.5 noExplosion 0 0 noExplosionSound 0 0 @@ -96,42 +91,23 @@ explosionRingScaleFactorChargeMult 0 0 explosionShellScaleFactor 0 0.95 explosionShellScaleFactorChargeMult 0 0 -noExplosion 1 0 -noExplosionSound 1 0 -explosionRing 1 1 -explosionShell 1 1 -explosionModel 1 "models/weaphits/sphere_hi.md3" -explosionShader 1 "LightningBallExplosionShader" -explosionRocks 1 10 -explosionSmoke 1 1 -explosionSparks 1 20 -explosionSmokeRadius 1 200 -explosionSmokeLife 1 1500 -explosionSmokeSpeed 1 10 -explosionScaleFactor 1 3 -explosionScaleFactorChargeMult 1 0 -explosionRingScaleFactor 1 3 -explosionRingScaleFactorChargeMult 1 0 -explosionShellScaleFactor 1 6 -explosionShellScaleFactorChargeMult 1 0 +noExplosion 1 1 noExplosion 2 0 noExplosionSound 2 0 explosionRing 2 1 explosionShell 2 1 -explosionModel 2 "models/weaphits/sphere_hi.md3" -explosionShader 2 "LStormExplosionShader" explosionRocks 2 10 explosionSmoke 2 1 explosionSparks 2 20 explosionSmokeRadius 2 200 -explosionSmokeLife 2 500 +explosionSmokeLife 2 1500 explosionSmokeSpeed 2 10 -explosionScaleFactor 2 0.95 +explosionScaleFactor 2 3 explosionScaleFactorChargeMult 2 0 -explosionRingScaleFactor 2 0.95 +explosionRingScaleFactor 2 3 explosionRingScaleFactorChargeMult 2 0 -explosionShellScaleFactor 2 0.95 +explosionShellScaleFactor 2 6 explosionShellScaleFactorChargeMult 2 0 noExplosion 3 0 @@ -139,24 +115,38 @@ noExplosionSound 3 0 explosionRing 3 1 explosionShell 3 1 explosionModel 3 "models/weaphits/sphere_hi.md3" -explosionShader 3 "MantisExplosionShader" +explosionShader 3 "earth_seeker_explosion" explosionRocks 3 10 -explosionSmoke 3 3 +explosionSmoke 3 1 explosionSparks 3 20 explosionSmokeRadius 3 200 -explosionSmokeLife 3 1500 +explosionSmokeLife 3 500 explosionSmokeSpeed 3 10 -explosionScaleFactor 3 3 -explosionScaleFactorChargeMult 3 1 -explosionRingScaleFactor 3 4 -explosionRingScaleFactorChargeMult 3 1 -explosionShellScaleFactor 3 6 -explosionShellScaleFactorChargeMult 3 1 +explosionScaleFactor 3 0.95 +explosionScaleFactorChargeMult 3 0 +explosionRingScaleFactor 3 0.95 +explosionRingScaleFactorChargeMult 3 0 +explosionShellScaleFactor 3 0.95 +explosionShellScaleFactorChargeMult 3 0 -noExplosion 4 1 +noExplosion 4 0 +noExplosionSound 4 0 +explosionRing 4 1 +explosionShell 4 1 explosionModel 4 "models/weaphits/sphere_hi.md3" -explosionShader 4 "AGAAttackShader" -explosionScaleFactor 4 1 -explosionScaleFactorChargeMult 4 2 +explosionShader 4 "PowerWaveExplosionShader" +explosionRocks 4 10 +explosionSmoke 4 3 +explosionSparks 4 20 +explosionSmokeRadius 4 200 +explosionSmokeLife 4 1500 +explosionSmokeSpeed 4 10 +explosionScaleFactor 4 3 +explosionScaleFactorChargeMult 4 1 +explosionRingScaleFactor 4 4 +explosionRingScaleFactorChargeMult 4 1 +explosionShellScaleFactor 4 6 +explosionShellScaleFactorChargeMult 4 1 end + diff --git a/cfgs/q3config.cfg b/cfgs/q3config.cfg deleted file mode 100644 index ed45439..0000000 --- a/cfgs/q3config.cfg +++ /dev/null @@ -1,333 +0,0 @@ -// generated by quake, do not modify -unbindall -bind TAB "+scores" -bind ENTER "+button2" -bind ESCAPE "togglemenu" -bind SPACE "+moveup" -bind + "sizeup" -bind - "sizedown" -bind / "weapnext" -bind 0 "weapon 10" -bind 1 "weapon 1bind 2 weapon 2bind 3 weapon 3bind 4 weapon 4bind 5 weapon 5" -bind 2 "weapon 2" -bind 3 "weapon 3" -bind 4 "weapon 4" -bind 5 "weapon 5" -bind 6 "weapon 6" -bind 7 "weapon 7" -bind 8 "weapon 8" -bind 9 "weapon 9" -bind = "sizeup" -bind [ "weapprev" -bind \ "+mlook" -bind ] "weapnext" -bind _ "sizedown" -bind ` "toggleconsole" -bind a "+moveleft" -bind b "vstr piccolo" -bind c "+movedown" -bind d "+moveright" -bind f "+button12" -bind m "vstr ginu" -bind n "vstr goku" -bind o "kiusetoggle" -bind s "+back" -bind t "messagemode" -bind w "+forward" -bind ~ "toggleconsole" -bind CAPSLOCK "+button9" -bind PAUSE "pause" -bind UPARROW "+forward" -bind DOWNARROW "+back" -bind LEFTARROW "+left" -bind RIGHTARROW "+right" -bind ALT "+button7" -bind CTRL "+button10" -bind SHIFT "+button8" -bind DEL "+lookdown" -bind PGDN "+lookup" -bind END "centerview" -bind F1 "vote yes" -bind F2 "vote no" -bind F3 "ui_teamorders" -bind F4 "vstr vegeta" -bind F5 "vstr beb" -bind F6 "vstr beb" -bind F7 "vstr gotenks" -bind F8 "vstr trunks" -bind F9 "vstr freeza1" -bind F10 "vstr cell" -bind F11 "screenshot" -bind F12 "vstr freeza1" -bind MOUSE1 "+attack" -bind MOUSE2 "+button9" -bind MOUSE3 "+zoom" -bind MWHEELDOWN "weapnext" -bind MWHEELUP "weapprev" -seta g_allowSpectatorChat "1" -seta g_doWarmup "0" -seta cg_lowpolysphere "0" -seta cg_lifedeaths "153" -seta cg_lifekills "109" -seta cg_stfu "0" -seta cg_superdeformed "0" -seta cg_permaglowUltimate "1" -seta cg_smallOwnAura "0" -seta cg_spriteAura "0" -seta cg_simpleHUD "0" -seta cg_lightweightAuras "0" -seta ui_bigFont "0.4" -seta ui_smallFont "0.25" -seta cg_noTaunt "0" -seta cg_smoothClients "0" -seta cg_scorePlums "1" -seta cg_cameraOrbitDelay "50" -seta cg_noVoiceText "0" -seta cg_noVoiceChats "0" -seta cg_teamChatsOnly "0" -seta cg_drawFriend "1" -seta cg_drawTeamOverlay "0" -seta cg_deferPlayers "1" -seta cg_forceSkin "0" -seta cg_forceModel "0" -seta cg_teamChatHeight "0" -seta cg_teamChatTime "3000" -seta cg_bobroll "0.002" -seta cg_bobpitch "0.002" -seta cg_bobup "0.005" -seta cg_runroll "0.005" -seta cg_runpitch "0.002" -seta cg_railTrailTime "400" -seta cg_lagometer "1" -seta cg_simpleItems "0" -seta cg_crosshairY "0" -seta cg_crosshairX "0" -seta cg_crosshairHealth "1" -seta cg_crosshairSize "24" -seta cg_drawRewards "1" -seta cg_drawAttacker "1" -seta cg_drawAmmoWarning "1" -seta cg_drawIcons "1" -seta cg_draw3dIcons "0" -seta cg_drawSnapshot "0" -seta cg_drawFPS "0" -seta cg_drawTimer "0" -seta cg_drawStatus "1" -seta cg_draw2D "1" -seta cg_gibs "1" -seta cg_stereoSeparation "0.4" -seta cg_fov "90" -seta cg_zoomfov "22.5" -seta cg_drawGun "1" -seta cg_playHitSound "0" -seta cg_flytilt "1" -seta cg_lightExplosions "1" -seta cg_lightAuras "1" -seta cg_transformationAura "1" -seta cg_thirdPersonRange "110" -seta cg_thirdPersonHeight "-60" -seta g_hitStun "1" -seta g_basePL "756" -seta cg_explosionRing "1" -seta cg_stableCrosshair "0" -seta cg_musicUnpacked "1" -seta cg_crosshairColor "7" -seta cg_particles "1" -seta cg_explosionSmoke "1" -seta cg_explosionShell "1" -seta cg_drawKiWarning "1" -seta cg_chargeupAlert "1" -seta cg_bigExplosions "1" -seta cg_beamTrail "25" -seta cg_kiTrail "50" -seta cg_highPolyAura "1" -seta cg_polygonAura "1" -seta cg_drawOwnModel "0" -seta cg_fixedThirdPerson "1" -seta cg_thirdPerson "1" -seta cg_shadows "1" -seta sv_pure "1" -seta net_socksPassword "" -seta net_socksUsername "" -seta net_socksPort "1080" -seta net_socksServer "" -seta net_socksEnabled "0" -seta net_noipx "0" -seta net_noudp "0" -seta server16 "" -seta server15 "" -seta server14 "" -seta server13 "" -seta server12 "" -seta server11 "" -seta server10 "" -seta server9 "" -seta server8 "" -seta server7 "" -seta server6 "" -seta server5 "" -seta server4 "" -seta server3 "" -seta server2 "" -seta server1 "" -seta cg_marks "1" -seta cg_drawCrosshairNames "1" -seta cg_drawCrosshair "3" -seta cg_brassTime "2500" -seta ui_browserShowEmpty "1" -seta ui_browserShowFull "1" -seta ui_browserSortKey "4" -seta ui_browserGameType "0" -seta ui_browserMaster "1" -seta g_spSkill "2" -seta g_spVideos "" -seta g_spAwards "" -seta g_spScores5 "" -seta g_spScores4 "" -seta g_spScores3 "" -seta g_spScores2 "" -seta g_spScores1 "" -seta ui_ctf_friendly "0" -seta ui_ctf_timelimit "30" -seta ui_ctf_capturelimit "8" -seta ui_team_friendly "1" -seta ui_team_timelimit "20" -seta ui_team_fraglimit "0" -seta ui_tourney_timelimit "15" -seta ui_tourney_fraglimit "0" -seta ui_ffa_timelimit "0" -seta ui_ffa_fraglimit "35" -seta com_soundMegs "8" -seta s_mixPreStep "0.05" -seta s_mixahead "0.2" -seta s_khz "22" -seta s_doppler "1" -seta s_separation "0.5" -seta s_musicvolume "0.25" -seta s_volume "0.8" -seta vid_ypos "22" -seta vid_xpos "3" -seta r_lastValidRenderer "GeForce 6700 XL/PCI/SSE2/3DNOW!" -seta r_primitives "0" -seta r_railSegmentLength "32" -seta r_railCoreWidth "15" -seta r_railWidth "16" -seta r_facePlaneCull "1" -seta r_gamma "1" -seta r_swapInterval "0" -seta r_textureMode "GL_LINEAR_MIPMAP_NEAREST" -seta r_finish "0" -seta r_dlightBacks "1" -seta r_dynamiclight "1" -seta r_drawSun "0" -seta r_fastsky "0" -seta r_ignoreGLErrors "1" -seta r_flares "0" -seta r_lodbias "0" -seta r_lodCurveError "250" -seta r_ignoreFastPath "1" -seta r_smp "0" -seta r_subdivisions "4" -seta r_vertexLight "0" -seta r_simpleMipMaps "1" -seta r_customaspect "1" -seta r_customheight "1024" -seta r_customwidth "1600" -seta r_fullscreen "1" -seta r_mode "3" -seta r_ignorehwgamma "0" -seta r_overBrightBits "1" -seta r_depthbits "0" -seta r_stencilbits "8" -seta r_stereo "0" -seta r_colorbits "0" -seta r_texturebits "0" -seta r_detailtextures "1" -seta r_roundImagesDown "1" -seta r_picmip "1" -seta r_ext_texture_env_add "1" -seta r_ext_compiled_vertex_array "1" -seta r_ext_multitexture "1" -seta r_ext_gamma_control "1" -seta r_ext_compressed_textures "0" -seta r_allowExtensions "1" -seta r_glDriver "opengl32" -seta cg_viewsize "100" -seta cg_predictItems "1" -seta cl_punkbuster "0" -seta cl_anonymous "0" -seta sex "male" -seta handicap "100" -seta color2 "5" -seta color1 "4" -seta g_blueTeam "Pagans" -seta g_redTeam "Stroggs" -seta team_headmodel "*james" -seta team_model "james" -seta headmodel "bfp1-freeza" -seta model "bfp3-gokussj5/default" -seta snaps "20" -seta rate "25000" -seta name "EBFP Freak" -seta cl_maxPing "800" -seta m_filter "0" -seta m_side "0.25" -seta m_forward "0.25" -seta m_yaw "0.022" -seta m_pitch "0.022000" -seta cg_autoswitch "1" -seta r_inGameVideo "1" -seta cl_allowDownload "0" -seta cl_freelook "1" -seta cl_mouseAccel "0" -seta sensitivity "5" -seta cl_run "1" -seta cl_packetdup "1" -seta cl_maxpackets "30" -seta cl_pitchspeed "140" -seta cl_yawspeed "140" -seta sv_strictAuth "1" -seta sv_lanForceRate "1" -seta sv_master5 "" -seta sv_master4 "" -seta sv_master3 "" -seta sv_master2 "" -seta sv_floodProtect "1" -seta sv_maxPing "0" -seta sv_minPing "0" -seta sv_maxRate "0" -seta sv_punkbuster "0" -seta sv_hostname "English Bid for Power 2.0" -seta vm_ui "2" -seta vm_game "2" -seta vm_cgame "2" -seta joy_threshold "0.150000" -seta in_joyBallScale "0.02" -seta in_joystick "0" -seta in_logitechbug "0" -seta in_mouse "1" -seta in_mididevice "0" -seta in_midichannel "1" -seta in_midiport "1" -seta in_midi "0" -seta com_introplayed "1" -seta com_blood "1" -seta com_maxfps "85" -seta com_hunkMegs "150" -seta dmflags "0" -seta fraglimit "35" -seta timelimit "0" -seta sv_maxclients "8" -seta cm_playerCurveClip "1" -seta g_maxGameClients "0" -seta capturelimit "0" -seta g_friendlyFire "0" -seta g_teamAutoJoin "0" -seta g_teamForceBalance "0" -seta g_warmup "20" -seta g_log "games.log" -seta g_logSync "0" -seta g_banIPs "" -seta g_filterBan "1" -seta g_allowVote "1" -seta com_zoneMegs "16" diff --git a/docs/Create_Custom_Models.md b/docs/Create_Custom_Models.md index 667ac99..823450e 100644 --- a/docs/Create_Custom_Models.md +++ b/docs/Create_Custom_Models.md @@ -297,6 +297,11 @@ treated as a sprite. Has no effect for hitscan attacks. The rotation of the missile.

+- ```missileModelRotation [attack index] [float]``` + +The rotation of the missile model. Set with float number: between 0.1 and 999.0 ... +

+ - ```missileSpinHoriz [attack index] [0/1]``` Used to switch the axis of rotation on the missile. diff --git a/docs/bfp_attacksets_config_file.md b/docs/bfp_attacksets_config_file.md new file mode 100644 index 0000000..f797f0f --- /dev/null +++ b/docs/bfp_attacksets_config_file.md @@ -0,0 +1,29 @@ +# Attacksets Config File + +The attackset config file (*bfp_attacksets.cfg*) is used to set attacks to every player model prefix. +Reads *bfp_attacksets.cfg* once in the server at the start after reading *bfp_weapon.cfg*. + +### attackset + +- ```attackset [int]```
+Attackset identifier for this group. + +### attack + +- ```attack [attack index] [weaponNum]```
+Sets the attack in this index, weaponNum is the attack identifier where helds attack properties. + +### modelPrefix + +- ```modelPrefix [string]```
+Sets prefix for this player model group which starts with this prefix. + +### defaultModel + +- ```defaultModel [string]```
+Sets default player model if it loads by using only the [modelPrefix](#modelPrefix) string in console. + +### end + +- ```end```
+Should be the last word in the *bfp_attacksets.cfg* file. \ No newline at end of file diff --git a/docs/bfp_cvars_task.md b/docs/bfp_cvars_task.md deleted file mode 100644 index 3b8ccef..0000000 --- a/docs/bfp_cvars_task.md +++ /dev/null @@ -1,73 +0,0 @@ -# BFP CVARS - -## PENDING: - -- g_basepl [1-999]: set the starting power level from one thousand to one mil. -- g_blockLength = "3" -- g_blockDelay = "2" -- g_kiChargePct = "15" -- g_kiCharge = "0" -- g_kiRegenPct = "0.6" -- g_kiRegen = "0" -- g_blockCostPct = "3" -- g_blockCost = "0" -- g_boostCostPct = "0" -- g_boostCost = "150" -- g_flightCostPct = "0" -- g_flightCost = "50" -- g_chargeDelay = "250" -- g_meleeRange = "32" -- g_meleeDiveRange = "700" -- g_meleeDamage = "10" - -#### Cvar Gametypes: - -- g_gametype 3 = sur (survival) -- g_gametype 4 = ooz (oozaru, in original BFP, that hasn't been added on UI, but add it anyway) -- g_gametype 5 = tdm (team deathmatch) // originally, in Q3, this number is 3 -- g_gametype 6 = lms (last man standing) -- g_gametype 7 = ctf (capture the flag) // originally, in Q3, this number is 4 - -... and maybe more, look in [cvar_bfp_list.txt](docs/cvar_bfp_list.txt) - - -## WIP: - -- g_hitStun [0/1]: turn on or off the melee hit stun. -- cg_drawKiWarning [0/1]: turn on or off the low ki warning. - -#### Cvar Gametypes: - -- g_gametype 2 = single player (in original BFP, that doesn't show in UI, so make Single Player option hidden) - -## DONE BUT NOT IN CGAME: - -- cg_stfu [0/1]: disable character voices when firing attacks. -- cg_lowpolysphere [0/1]: force the use of a lower polycount sphere. -- cg_kitrail [0-99]: set the length of the ki trail. 0 turns it off. -- cg_lightauras [0/1]: turn on or off the aura dynamic lights. -- cg_lightexplosions [0/1]: turn on or off the explosion dynamic lights. -- cg_chargeupAlert [0/1]: turn on or off the "ready" message when charging attacks. -- cg_explosionShell [0/1]: turn on or off the explosion shell. -- cg_explosionSmoke [0/1]: turn on or off the explosion smoke. -- cg_explosionRing [0/1]: turn on or off the explosion ring. -- cg_particles [0/1]: turn on or off particle effects. -- cg_playHitSound [0/1]: turn on or off the q3 hit "ping". - - -## COMPLETED: - -- [x] ~~cg_yrgolroxor~~ -- [x] ~~cg_thirdPersonHeight~~ -- [x] ~~cg_fixedThirdPerson~~ -- [x] ~~cg_drawOwnModel~~ -- [x] ~~cg_stableCrosshair~~ -- [x] ~~cg_musicUnpacked~~ -- [x] ~~cg_crosshairColor~~ -- [x] ~~cg_crosshairHealth~~ -- [x] ~~cg_flytilt~~ - -#### Cvar Gametypes: - -- [x] ~~g_gametype 0 = dm (aka FFA)~~ (already in Q3 code) -- [x] ~~g_gametype 1 = 1v1 (tournament)~~ (already in Q3 code) \ No newline at end of file diff --git a/docs/bfp_networking.md b/docs/bfp_networking.md new file mode 100644 index 0000000..420412a --- /dev/null +++ b/docs/bfp_networking.md @@ -0,0 +1,191 @@ +The following stuff is used in original BFP networking and has been found using engine code. Although optimizations or ways to not use network flags or values ​​are found, the original flags and indexes must be present to see how it was envisioned. + +* PMF_ flags: the same as Quake 3 +* EF_ flags: the same as Quake 3 +* STAT_ indexes: + * STAT_0 = health + * STAT_3 = armor + * STAT_4 = dead yaw + * STAT_5 = bit mask for client ready intermission + * STAT_6 = powerlevel + * STAT_7 = flight jump anim transition seconds, the maximum is until 21 sec and stops changing to 0, even when stop flying also reproduces this stat index like starting to fly (looks weird) + * STAT_8 = ki + * STAT_9 = maximum ki + * STAT_10 = melee attack time + * STAT_11 = a weird timer with 2 msec appears in the first 3 msec of melee attack time (STAT_10) when melee attack time is starting from 25 msec (what the hell?) + * STAT_13 = active firing state (for rdmissile and beam attack types with attackCharge) + * STAT_14 = force field weapon state + * STAT_15 = fly tilt: moving left is lesser than 0 until -80, moving right is more than 0 until 80 +* PERS_ (persistant) indexes: + * PERS_0 = score + * PERS_1 = total points damage inflicted so damage beeps can sound on change + * PERS_2 = rank + * PERS_3 = player team + * PERS_4 = spawn count (respawn) + * PERS_7 = health/armor of last person we attacked + * PERS_8 = count of the number of times you died + * PERS_11 = defend awards (defending the flag in CTF) + * PERS_13 = ??? (Q3: kills with the guantlet) + * PERS_14 = powerlevel + * PERS_15 = ??? (it appears when spawning at the first time of all in-game) +* PW_ (powerups) indexes: + * PW_0 = that's where PW_HASTE and PW_BATTLESUIT are marked as PW_NONE after picking up + * PW_1 = PW_QUAD + * PW_2 = PW_REDFLAG placed on PW_BATTLESUIT + * PW_3 = PW_BLUEFLAG placed on PW_HASTE + * PW_5 = PW_FLIGHT reused for flight enabled/disabled + * PW_6 = ki recharge + * PW_7 = ki use/toggle + * PW_8 = blocking seconds (to defend yourself from melee, ki attacks: beams, projectiles, ...) + * PW_9 = melee toggle + * PW_10 = hit stun + * PW_11 = ki attack charge points + * PW_12 = monster flag (g_gametype 4) + * PW_13 = beam firing state? + * PW_14 = jump (what the hell?) +* WP_ (weapons) indexes: + * WP_0, WP_1, WP_2, WP_3 and WP_4 are the weapons in the attack selection + * WP_4 (WP_GRENADE_LAUNCHER) = as the last ki attack, but also when the player is being attacked or damaged, begins from 2000 as milliseconds until 0, looks like a timer, the purpose remains unknown + * WP_5 (WP_ROCKET_LAUNCHER) = ki recharge time to enable + * WP_6 (WP_LIGHTNING) = hit stun delay (after receiving a hit stun) + * WP_7 (WP_RAILGUN) = block delay (after using block) + * WP_8 (WP_PLASMAGUN) = ki use/boost toogle + * WP_9 (WP_BFG) = flight toggle key control (to avoid spamming the enable flight key, just enable once and disable once) + * WP_10 (WP_GRAPPLING_HOOK) = blind seconds + * WP_11 = for rapid ki attacks like ki storm (alternates: -1 and 1) + * WP_13 = toggles if the player can use zanzoken or not + * WP_14 = directional left and right keys to move left or right while pressing, adds time msec, not sure if that's a timer to handle WP_13 for zanzoken + * WP_15 = enables beam struggle + +* EV_* (events) indexes: +```c + * EV_NONE // 0 + * EV_UNUSED_INDEX1 // 1 + * EV_UNUSED_INDEX2 // 2 + * EV_UNUSED_INDEX3 // 3 + * EV_UNUSED_INDEX4 // 4 + * EV_UNUSED_INDEX5 // 5 + * EV_UNUSED_INDEX6 // 6 + * EV_UNUSED_INDEX7 // 7 + * EV_UNUSED_INDEX8 // 8 + * EV_UNUSED_INDEX9 // 9 + * EV_MELEE_READY // 10, preparing melee + * EV_MELEE // 11, melee attack + * EV_UNUSED_INDEX12 // 12 + * EV_TIER_RESET // 13, reset tier when the player respawns and changes to the default or a bit less ki energy? + * EV_TIER_0 // 14, EV_TIER_0-4 (14-18), when the player frags, increases their PL and obtains a new skill (in the last tier, transforms) + * EV_TIER_1 // 15 + * EV_TIER_2 // 16 + * EV_TIER_3 // 17 + * EV_TIER_4 // 18 + * EV_ZANZOKEN_IN // 19, Short-Range Teleport (Zanzoken) + * EV_ZANZOKEN_OUT // 20, stop/leaves zanzoken + * EV_KI_BOOST // 21, enables aura/ki trail + * EV_ENABLE_FLIGHT // 22, enable flight + * EV_FOOTSTEP // 23 + * EV_FOOTSTEP_METAL // 24 + * EV_FOOTSPLASH // 25 + * EV_FOOTWADE // 26 + * EV_SWIM // 27 + * EV_STEP_4 // 28 + * EV_STEP_8 // 29 + * EV_STEP_12 // 30 + * EV_STEP_16 // 31 + * EV_FALL_SHORT // 32 + * EV_FALL_MEDIUM // 33 + * EV_FALL_FAR // 34 + * EV_JUMP_PAD // 35 + * EV_JUMP // 36 + * EV_JUMP_2 // 37 + * EV_WATER_TOUCH // 38 + * EV_WATER_LEAVE // 39 + * EV_WATER_UNDER // 40 + * EV_WATER_CLEAR // 41 + * EV_ITEM_PICKUP // 42 + * EV_GLOBAL_ITEM_PICKUP // 43 + * EV_NOAMMO // 44 + * EV_CHANGE_WEAPON // 45 + * EV_FIRE_WEAPON // 46 + * EV_USE_ITEM0 // 47 + * EV_USE_ITEM1 // 48 + * EV_USE_ITEM2 // 49 + * EV_USE_ITEM3 // 50 + * EV_USE_ITEM4 // 51 + * EV_USE_ITEM5 // 52 + * EV_USE_ITEM6 // 53 + * EV_USE_ITEM7 // 54 + * EV_USE_ITEM8 // 55 + * EV_USE_ITEM9 // 56 + * EV_USE_ITEM10 // 57 + * EV_USE_ITEM11 // 58 + * EV_USE_ITEM12 // 59 + * EV_USE_ITEM13 // 60 + * EV_USE_ITEM14 // 61 + * EV_USE_ITEM15 // 62 + * EV_ITEM_RESPAWN // 63 + * EV_ITEM_POP // 64 + * EV_PLAYER_TELEPORT_IN // 65 + * EV_PLAYER_TELEPORT_OUT // 66 + * EV_GRENADE_BOUNCE // 67 + * EV_GENERAL_SOUND // 68 + * EV_GLOBAL_SOUND // 69 + * EV_GLOBAL_TEAM_SOUND // 70 + * EV_BULLET_HIT_FLESH // 71 + * EV_BULLET_HIT_WALL // 72 + * EV_MISSILE_HIT // 73 + * EV_MISSILE_MISS // 74 + * EV_MISSILE_MISS_METAL // 75 + * EV_MISSILE_DETONATE // 76, in some moment, ki attack beam/projectile + * EV_RAILTRAIL // 77 + * EV_SHOTGUN // 78 + * EV_UNUSED_INDEX79 // 79 + * EV_PAIN // 80 + * EV_DEATH1 // 81 + * EV_DEATH2 // 82 + * EV_DEATH3 // 83 + * EV_OBITUARY // 84 + * EV_POWERUP_QUAD // 85 + * EV_POWERUP_BATTLESUIT // 86 + * EV_UNUSED_INDEX87 // 87 + * EV_GIB_PLAYER // 88 + * EV_UNUSED_INDEX89 // 89 + * EV_UNUSED_INDEX90 // 90 + * EV_SCOREPLUM // 91 + * EV_TAUNT // 92 + * EV_UNUSED_INDEX93 // 93 + * EV_UNUSED_INDEX94 // 94 + * EV_UNUSED_INDEX95 // 95 + * EV_UNUSED_INDEX96 // 96 + * EV_DEBUG_LINE // 97 + * EV_STOPLOOPINGSOUND // 98 + * EV_BEAM_STRUGGLE // 99, beam struggle +``` + +* bg_itemlist indexes: +```c + * NULL // 0 + * item_armor_shard // 1 + * item_armor_combat // 2 + * item_armor_body // 3 + * item_health_small // 4 + * item_health // 5 + * item_health_large // 6 + * item_health_mega // 7 + * weapon // 8 + * weapon // 9 + * weapon // 10 + * weapon // 11 + * weapon // 12 + * NULL // from 13 until 116 + * holdable_teleporter // 117 + * holdable_medkit // 118 + * item_quad // 119 + * item_enviro // 120 + * item_haste // 121 + * item_invis // 122 + * team_CTF_redflag // 123 + * team_CTF_blueflag // 124 +``` + +In original BFP, when the players aim with the crosshair against their opponents to show their powerlevel, uses `entityState_t->time2` (32-bit integer size) instead `entityState_t->frame` (16-bit integer size). +With `entityState_t->frame` was enough for that size from 0 until 1000. diff --git a/docs/bfp_weapon_config_file.md b/docs/bfp_weapon_config_file.md new file mode 100644 index 0000000..f20bc06 --- /dev/null +++ b/docs/bfp_weapon_config_file.md @@ -0,0 +1,262 @@ +# Weapon Config File + +The weapon config file (*bfp_weapon.cfg* and *bfp_weapon2.cfg*) is used to define attacks, every attack has its own speed, radius, damage, ... +Reads *bfp_weapon.cfg*, and after *bfp_weapon2.cfg* (if exists), once in the server at the start. There are some properties used in `bg_pmove.c`. + +### (attack_name) + +- `(attack_name)`
+Attack tag, starts to read the attack properties below and continues setting the properties of this attack until the next attack tag or the [end](#end) of file. + +### weaponNum + +- ```weaponNum [int]```
+Attack identifier. + +### attackType + +- ```attackType [missile/rdmissile/beam/sbeam/hitscan/forcefield]```
+Sets attack type. + - `missile` shots a projectile. + - `rdmissile` is like a projectile that splits projectiles by the number of ki charge points. Requires another attack to set, see [explosionSpawn](#explosionSpawn). + - `beam` is a steerable beam you can aim, if something solid touches the beam trail, beam explodes. If another beam collides against other beam, creates a beam struggle. + - `sbeam` is another steerable beam you can aim, but only when you hold the attack key. It doesn't work for beam struggles. + - `hitscan` is used for instant shots and short range attacks, like shockwave, lightning and rail trail attacks. + - `forcefield` creates a magnetic field or an explosion at the center of the attacker. + +### weaponTime + +- ```weaponTime [int]```
+Attack timer, sets milliseconds for the attack, it fires after ending these milliseconds. + +### randomWeaponTime + +- ```randomWeaponTime [int]```
+Sets and adds milliseconds for the [weaponTime](#weaponTime), randomizes attack timer calculation. + +### kiCostAsPct + +- ```kiCostAsPct [0/1]```
+Enables ki cost percentage to calculate, see [kiPct](#kiPct). + +### kiPct + +- ```kiPct [0.0 - 1.0 (float)]```
+Calculates maximum ki user multiplied by the ki percentage. Works if it's more than 0 and lesser than 1. + +### kiCost + +- ```kiCost [0-10000]```
+Ki cost to reduce the user's ki. Works without ki percentage. + +### chargeAttack + +- ```chargeAttack [0/1]```
+Enables the charge attack depending the ki charge points to release the attack above or equal the minimum ki charge points, see [minCharge](#minCharge). + +### chargeAutoFire + +- ```chargeAutoFire [0/1]```
+Releases the attack with every charge points released after [weaponTime](#weaponTime) milliseconds. + +### minCharge + +- ```minCharge [0-6]```
+Minimum ki charge points to release the attack with [chargeAttack](#chargeAttack). + +### maxCharge + +- ```maxCharge [0-6]```
+Maximum ki charge points for the attack with [chargeAttack](#chargeAttack). + +### damage + +- ```damage [int]```
+Attack damage. + +### splashDamage + +- ```splashDamage [int]```
+Attack splash damage from the explosion. + +### chargeDamageMult + +- ```chargeDamageMult [int]```
+Charge damage multiplier, calculates the damage multiplied per ki charge points of the attack. + +### maxDamage + +- ```maxDamage [int]```
+Maximum damage deal for the attack. + +### radius + +- ```radius [int]```
+Projectile radius. + +### explosionRadius + +- ```explosionRadius [int]```
+Explosion radius. + +### chargeRadiusMult + +- ```chargeRadiusMult [int]```
+Charge radius multiplier for the projectile, calculates the radius for the projectile multiplied per ki charge points of the attack. + +### chargeExpRadiusMult + +- ```chargeExpRadiusMult [int]```
+Charge radius multiplier for the explosion, calculates the explosion radius multiplied per ki charge points of the attack. + +### maxRadius + +- ```maxRadius [int]```
+Maximum radius for the projectile. + +### maxExpRadius + +- ```maxExpRadius [int]```
+Maximum explosion radius. + +### missileSpeed + +- ```missileSpeed [int]```
+Projectile speed. + +### homing + +- ```homing [0.0 - 1.0 (float)]```
+Homing projectile trajectory. If lesser, rotates less. Works if it's more than 0 and lesser than 1. + +### homingRange + +- ```homingRange [float]```
+Homing projectile range. The radius of the homing projectile to chase opponents. + +### range + +- ```range [float]```
+Attack range. Applicable only to hitscan attack type. + +### loopingAnim + +- ```loopingAnim [0/1]```
+Looping attack animation for the player model. If enabled, the player remains in the strike attack animation without returning to the prepare attack animation. + +### noAttackAnim + +- ```noAttackAnim [0/1]```
+No attack animation for the player model. If enabled, the player remains in the strike attack animation skipping prepare attack animation even at the start. + +### alternatingXOffset + +- ```alternatingXOffset [0/1]```
+Alternates projectile fire, switching from side to side: left to right, right to left and thus. + +### randYOffset + +- ```randYOffset [float]```
+Randomizes the upward vector offset of projectile fire. + +### randXOffset + +- ```randXOffset [float]```
+Randomizes the horizontal vector offset of projectile fire. + +### coneOfFireX + +- ```coneOfFireX [int]```
+Sets the horizontal vector where the projectile is headed. + +### coneOfFireY + +- ```coneOfFireY [int]```
+Sets the upward vector where the projectile is headed. + +### piercing + +- ```piercing [0/1]```
+Enable piercing projectile. Applicable only to missile attack type, it won't work flawlessly for beam and sbeam attack types. Pierces the opponent by hitting 4 times if it stands in the same point. + +### reflective + +- ```reflective [0/1]```
+Enable reflective attack, reflects attacker' projectiles where the user aims. Applicable only to hitscan attack type with [range](#range). + +### priority + +- ```priority [int]```
+Projectile priority. Not applicable to hitscan and forcefield attack types. Not applicable to two beam attack type collisions, these create a beam struggle. If more, breaks and explodes the opponent projectile. If both have the same priority, both break and explode. + +### blinding + +- ```blinding [0/1]```
+The attack blinds the opponent during 6 seconds. + +### missileGravity + +- ```missileGravity [int]```
+The projectile moves down depending on how much gravity is going down. Not applicable to hitscan and forcefield attack types. + +### missileAcceleration + +- ```missileAcceleration [float]```
+Adds projectile acceleration, speeds up per frame. Not aplicable to hitscan and forcefield attack types. If between 0.0 and 1.0, deceleration. + +### missileDuration + +- ```missileDuration [int]```
+Projectile lifetime. Not applicable to hitscan and forcefield attack types. + +### multishot + +- ```multishot [int]```
+Number of projectiles per shot. Applicable only to missile attack type. + +### bounces + +- ```bounces [0/1]```
+Enable projectile bouncing when colliding something solid. Applicable only to missile attack type. + +### bounceFriction + +- ```bounceFriction [float]```
+Adds bounce friction. Applicable only to missile attack type. If lesser, less bounce force. Applicable only to missile attack type. + +### noZBounce + +- ```noZBounce [0/1]```
+Fix the projectile vertical component to a deterministic bounce height, ignores [missileGravity](#missileGravity) property. Applicable only to missile attack type. + +### extraKnockback + +- ```extraKnockback [int]```
+Adds knockback while the opponent deals damage from the attack. Lesser than 0, less knockback. If -1000, no knockback. + +### railTrail + +- ```railTrail [0/1]```
+Instant rail trail, turns into like Q3 Railgun weapon. Applicable only to hitscan attack type. + +### movementPenalty + +- ```movementPenalty [int]```
+Movement penalty in seconds after using the attack, the user can't move after those seconds have passed. Applicable only to forcefield attack type. + +### explosionSpawn + +- ```explosionSpawn [weaponNum]```
+Sets the splitting projectile from rdmissile attack type. + +### end + +- ```end```
+Should be the last word in the *bfp_weapon.cfg* file. + +## ORIGINAL BFP NOTES +- [blinding](#blinding) is only applicable to forcefield attack type. +- beam attack type with [chargeAutoFire](#chargeAutoFire) detonates at the first moment and attacker deals damage. +- rdmissile attack type with [chargeAutoFire](#chargeAutoFire) splits the projectile at the first moment and attacker deals damage. +- There's a bug on the beam attack type with [piercing](#piercing), players are stuck in the charging/firing status and their beam/sbeam can't shot from their muzzle and are coming from zeroed origin. +- As for ```usesGravity [0/1]```, it's UNUSED. Theoretically, it was used during the development of the original mod and was never applied to the gameplay. diff --git a/docs/bind_bfp_list.txt b/docs/bind_bfp_list.txt deleted file mode 100644 index cbf8961..0000000 --- a/docs/bind_bfp_list.txt +++ /dev/null @@ -1,70 +0,0 @@ -]\bindlist -TAB "+scores" -ENTER "+button2" -ESCAPE "togglemenu" -SPACE "+moveup" -+ "sizeup" -- "sizedown" -/ "weapnext" -0 "weapon 10" -1 "weapon 1bind 2 weapon 2bind 3 weapon 3bind 4 weapon 4bind 5 weapon 5" -2 "weapon 2" -3 "weapon 3" -4 "weapon 4" -5 "weapon 5" -6 "weapon 6" -7 "weapon 7" -8 "weapon 8" -9 "weapon 9" -= "sizeup" -[ "weapprev" -\ "+mlook" -] "weapnext" -_ "sizedown" -` "toggleconsole" -a "+moveleft" -b "vstr piccolo" -c "+movedown" -d "+moveright" -f "fly" -l "+speed" -m "vstr ginu" -n "vstr goku" -o "kiusetoggle" -p "pause" -s "+back" -t "messagemode" -w "+forward" -~ "toggleconsole" -CAPSLOCK "+button9" -PAUSE "pause" -UPARROW "+forward" -DOWNARROW "+back" -LEFTARROW "+left" -RIGHTARROW "+right" -ALT "+button7" -CTRL "+button10" -SHIFT "+button8" -DEL "+lookdown" -PGDN "+lookup" -END "centerview" -F1 "vote yes" -F2 "vote no" -F3 "ui_teamorders" -F4 "vstr vegeta" -F5 "vstr beb" -F6 "vstr beb" -F7 "vstr gotenks" -F8 "vstr trunks" -F9 "vstr freeza1" -F10 "vstr cell" -F11 "screenshot" -F12 "vstr freeza1" -MOUSE1 "+attack" -MOUSE2 "+button9" -MOUSE3 "+zoom" -MWHEELDOWN "weapnext" -MWHEELUP "weapprev" -]\condump bind_bfp_list.txt -Dumped console text to bind_bfp_list.txt. - diff --git a/docs/cmd_bfp_list.txt b/docs/cmd_bfp_list.txt deleted file mode 100644 index edb4cfa..0000000 --- a/docs/cmd_bfp_list.txt +++ /dev/null @@ -1,239 +0,0 @@ -]\cmdlist -cmdlist -seta --scores --speed --moveright --movedown --button2 -clear --button7 --button10 -condump --forward --back --button8 -cvarlist -+button8 -voip -bind -loaddefered -teamtask -stats -teamvote -callteamvote -vote -callvote -setviewpos -addbot -levelshot -follow -team -noclip -notarget -god -give -votell -vosay_team -vosay -vtaunt -vtell -vsay_team -vsay -tell -say_team -say -kill -transformorbit -button12 -+fly --melee -+melee --block -+block --kiuse -+kiuse --kiboost -+kiboost -loaddeferred -startOrbit -tcmd -vtell_attacker -vtell_target -tell_attacker -tell_target -weapon -weapprev -weapnext -sizedown -sizeup --zoom -+zoom -+scores -viewpos -prevskin -nextskin -prevframe -nextframe -testmodel -testgun -exportCubemaps -gfxmeminfo -minimize -gfxinfo -screenshotJPEG -screenshot -modelist -modellist -skinlist -shaderlist -imagelist --attack -wait -which -touchFile -fdir -dir -path -map -ping -globalservers -music -s_info -s_stop -s_list -stopmusic -play -stopvideo -video -model -fs_referencedList -fs_openedList -showip -serverstatus -rcon -localservers -reconnect -connect -stoprecord -cinematic -demo -record -disconnect -vid_restart -snd_restart -clientinfo -configstrings -cmd --voiprecord -+voiprecord --mlook -+mlook --button14 -+button14 --button13 -+button13 --button12 -+button12 --button11 -+button11 -+button10 --button9 -+button9 -+button7 --button6 -+button6 --button5 -+button5 --button4 -+button4 --button3 -+button3 -+button2 --button1 -+button1 --button0 -+button0 -+attack -+speed -+moveright --moveleft -+moveleft --strafe -+strafe --lookdown -+lookdown --lookup -+lookup -+back -+forward --right -+right --left -+left -+movedown --moveup -+moveup -centerview -messagemode4 -messagemode3 -messagemode2 -messagemode -togglemenu -toggleconsole -exec -unbindall -rehashbans -net_restart -sayto -flushbans -exceptdel -bandel -exceptaddr -banaddr -listbans -killserver -spdevmap -spmap -devmap -sectorlist -map_restart -dumpuser -systeminfo -serverinfo -status -clientkick -kicknum -kickall -kickbots -banClient -banUser -kick -heartbeat -vminfo -vmprofile -in_restart -meminfo -game_restart -writeconfig -changeVectors -quit -setenv -bindlist -unbind -echo -vstr -execq -cvar_restart -cvar_modified -unset -reset -setu -sets -set -toggle -print -234 commands -]\condump cmd_bfp_list.txt -Dumped console text to cmd_bfp_list.txt. - diff --git a/docs/cvar_bfp_list.txt b/docs/cvar_bfp_list.txt deleted file mode 100644 index ee402e4..0000000 --- a/docs/cvar_bfp_list.txt +++ /dev/null @@ -1,200 +0,0 @@ -]\cg_ - cg_yrgolroxor = "0" - cg_particleAura = "0" - cg_timescaleFadeSpeed = "0" - cg_timescaleFadeEnd = "1" - cg_cameraOrbit = "0" - cg_stats = "0" - cg_thirdPersonAngle = "0" - cg_tracerlength = "100" - cg_tracerwidth = "1" - cg_tracerchance = "0.4" - cg_footsteps = "1" - cg_showmiss = "0" - cg_noplayeranims = "0" - cg_nopredict = "0" - cg_errordecay = "100" - cg_debugevents = "0" - cg_debugposition = "0" - cg_debuganim = "0" - cg_animspeed = "1" - cg_swingSpeed = "0.3" - cg_centertime = "3" - cg_gunZ = "0" - cg_gunY = "0" - cg_gunX = "0" - cg_ignore = "0" - cg_brassTime = "2500" - cg_drawCrosshair = "3" - cg_drawCrosshairNames = "1" - cg_marks = "1" - cg_thirdPerson = "1" - cg_fixedThirdPerson = "1" - cg_drawOwnModel = "0" - cg_polygonAura = "1" - cg_highPolyAura = "1" - cg_kiTrail = "50" - cg_beamTrail = "25" - cg_bigExplosions = "1" - cg_chargeupAlert = "1" - cg_drawKiWarning = "1" - cg_explosionShell = "1" - cg_explosionSmoke = "1" - cg_particles = "1" - cg_crosshairColor = "7" - cg_musicUnpacked = "1" - cg_stableCrosshair = "0" - cg_explosionRing = "1" - cg_thirdPersonHeight = "-60" - cg_thirdPersonRange = "110" - cg_transformationAura = "1" - cg_lightAuras = "1" - cg_lightExplosions = "1" - cg_flytilt = "1" - cg_playHitSound = "0" - cg_drawGun = "1" - cg_zoomfov = "22.5" - cg_fov = "90" - cg_gibs = "1" - cg_draw2D = "1" - cg_drawStatus = "1" - cg_drawTimer = "0" - cg_drawFPS = "0" - cg_drawSnapshot = "0" - cg_draw3dIcons = "0" - cg_drawIcons = "1" - cg_drawAmmoWarning = "1" - cg_drawAttacker = "1" - cg_drawRewards = "1" - cg_crosshairSize = "24" - cg_crosshairHealth = "1" - cg_crosshairX = "0" - cg_crosshairY = "0" - cg_simpleItems = "0" - cg_lagometer = "1" - cg_railTrailTime = "400" - cg_runpitch = "0.002" - cg_runroll = "0.005" - cg_bobup = "0.005" - cg_bobpitch = "0.002" - cg_bobroll = "0.002" - cg_teamChatTime = "3000" - cg_teamChatHeight = "0" - cg_forceModel = "0" - cg_forceSkin = "0" - cg_deferPlayers = "1" - cg_drawTeamOverlay = "0" - cg_drawFriend = "1" - cg_teamChatsOnly = "0" - cg_noVoiceChats = "0" - cg_noVoiceText = "0" - cg_cameraOrbitDelay = "50" - cg_scorePlums = "1" - cg_smoothClients = "0" - cg_noTaunt = "0" - cg_lightweightAuras = "0" - cg_simpleHUD = "0" - cg_spriteAura = "0" - cg_smallOwnAura = "0" - cg_permaglowUltimate = "1" - cg_superdeformed = "0" - cg_stfu = "0" - cg_lifekills = "183" - cg_lifedeaths = "212" - cg_lowpolysphere = "1" - cg_autoswitch = "1" - cg_predictItems = "1" - cg_viewsize = "100" - cg_stereoSeparation = "0" - cg_shadows = "1" -]\g_ - g_enableBreath = "0" - g_enableDust = "0" - g_blockLength = "3" - g_blockDelay = "2" - g_kiChargePct = "15" - g_kiCharge = "0" - g_kiRegenPct = "0.6" - g_kiRegen = "0" - g_blockCostPct = "3" - g_blockCost = "0" - g_boostCostPct = "0" - g_boostCost = "150" - g_flightCostPct = "0" - g_flightCost = "50" - g_maxSpawnPL = "999" - g_plKillBonusPct = "0.1" - g_chargeDelay = "250" - g_meleeRange = "32" - g_meleeDiveRange = "700" - g_meleeDamage = "10" - g_podiumDrop = "70" - g_podiumDist = "80" - g_motd = "" - g_debugAlloc = "0" - g_debugDamage = "0" - g_debugMove = "0" - g_inactivity = "0" - g_forcerespawn = "20" - g_weaponrespawn = "5" - g_quadfactor = "3" - g_knockback = "1000" - g_gravity = "800" - g_speed = "320" - g_needpass = "0" - g_password = "" - g_synchronousClients = "0" - g_restarted = "0" - g_noFlight = "0" - g_meleeOnly = "0" - g_botsFile = "" - g_arenasFile = "" - g_allowVote = "1" - g_filterBan = "1" - g_banIPs = "" - g_logSync = "0" - g_log = "games.log" - g_warmup = "20" - g_teamForceBalance = "0" - g_teamAutoJoin = "0" - g_friendlyFire = "0" - g_maxGameClients = "0" - g_spScores1 = "" - g_spScores2 = "" - g_spScores3 = "" - g_spScores4 = "" - g_spScores5 = "" - g_spAwards = "" - g_spVideos = "" - g_spSkill = "2" - g_basePL = "952" - g_hitStun = "1" - g_doWarmup = "0" - g_allowSpectatorChat = "1" - g_gametype = "0" - g_redTeam = "Stroggs" - g_blueTeam = "Pagans" -]\ui_ - ui_cdkeychecked = "0" - ui_spSelection = "" - ui_ffa_fraglimit = "2" - ui_ffa_timelimit = "1" - ui_tourney_fraglimit = "2" - ui_tourney_timelimit = "3" - ui_team_fraglimit = "9" - ui_team_timelimit = "2" - ui_team_friendly = "1" - ui_ctf_capturelimit = "8" - ui_ctf_timelimit = "30" - ui_ctf_friendly = "0" - ui_browserMaster = "1" - ui_browserGameType = "0" - ui_browserSortKey = "4" - ui_browserShowFull = "1" - ui_browserShowEmpty = "1" - ui_smallFont = "0.25" - ui_bigFont = "0.4" - ui_singlePlayerActive = "0" -]\condump cvar_bfp_list.txt -Dumped console text to cvar_bfp_list.txt. - diff --git a/docs/old_dev_journals/ansel_dev_journal.md b/docs/old_dev_journals/ansel_dev_journal.md new file mode 100644 index 0000000..a1391ce --- /dev/null +++ b/docs/old_dev_journals/ansel_dev_journal.md @@ -0,0 +1,71 @@ +# Another character design - 11/14/2001 by Ansel + +Hey +I just finished texturing Pyrate a few weeks ago, so I thought I'd get some concept sketches going for another character. + +"Ryuujin - Stands for Dragon God in japanese. He is, obviously, a god who has spent the past 3 millenia training his mind and his body. He rarely speaks, and is rarely seen, but he always shows up when there is trouble. His ki is "different," kind of mysterious and very powerful. He wields two scerpent-dragon swords whose blades are made of the same ki as a kienzan." (taken from pyrofragger's devjournal) + +ryuujin_concept_art + +Sorry for the bad image quality, I don't have a scanner. + +This is not a final concept, and as always feedback is welcome. + +-Ansel + + + + +# Concept Drawings - 09/04/2001 by Ansel + +As you probably already know, BFP will be having original characters, so I went and did a few concept drawings. Here is my first concept- + +c1noname_pyrate + +If you have any questions or comments, please stop by the forums. + +-Ansel + + + + +# New Icons - 07/19/2001 by Ansel + +I finished all of the new attack icons for BFP. I won't show them all, but here is three more. You already saw the kamahameha icon in Chris' journal. + +

+ bakuhatsuha +
+ bigbang +
+ sbc +

+ +By the way, the attacks in order are: Bakuhatsuha, SBC, and Big Bang Attack. + +-Ansel + + + + +# What happend? Gohan happend - 04/03/2001 by Ansel + +We promised that we would show sceens of the new models, here is one. +Its a screen of Pyrofragger's new Gohan model, skinned. +The model is seperated in this shot though, we didn't have time to get a render of him put together. + +bfpgohan02 + + +Expect renders of gohan and krillin's new skins soon. + +Ansel + + + + +# First Update - 04/03/2001 by Ansel + +Just finshed the Gohan and Krillin skins for the new models, which means my phase 1 work is done.
+Ansel + diff --git a/docs/old_dev_journals/anthony_dev_journal.md b/docs/old_dev_journals/anthony_dev_journal.md new file mode 100644 index 0000000..d5d4b0c --- /dev/null +++ b/docs/old_dev_journals/anthony_dev_journal.md @@ -0,0 +1,50 @@ +# Old DBZ BFP art - 01/21/2002 by Anthony + +Hey guys, seeing as how some fan paks are gonna be released with DBZ stuff that works in BFP, I thought I'd post some of the old drawings I did for the original character select screen. So anyway, an example: + +c-freeza + + +To see the others, go here: + + +[Ant's DBZ Stash](https://web.archive.org/web/20020210151755/http://www.antsin3d.com/dbz/) + + +Hope ya enjoy it, it would have been a nice select screen ^_^ +Oh yeah, if you'd like to see some other neat stuff(just some 3d), see [HERE](https://web.archive.org/web/20020210151755/http://www.antsin3d.com/tempcg) + + + + +# Play it chill - 01/18/2002 by Anthony + +What's up guys? The launch is going over like either a movie premier or New Year's Eve, or a mix of both. Play it cool and you'll get it. I counted 2500 people in line over at Fileplanet, and everyone else is getting just as hammered. We know one thing: you won't have any problems finding opponents! I hope you guys all enjoy it, as I'm sure you will. Remember, take it easy and everyone will get the mod. Don't want anyone to get hurt ;] Have a BLAST! + + + + +# Stuff - 08/02/2001 by Anthony + +The box art image will be released shortly. On another note, I'd like to request that you not send any money to me in any way, be it capital, goods, or services. I'm not going to play off your gratitude and anxiousness for this mod by asking for such. I would consider doing so disgusting. Naturally it's not my place to judge the souls of others who may wish to.... +PS: Of course, Icey's now gone, as are his journal entries which I was referring to above. We'll have more info sometime late next week, probably another happy announcement. + + + + +# Art - 04/17/2001 by Anthony + +Just a little update, I finished the box art I was working on. You'll all get to see it in due course, a little closer to release. Of course, I can still tease you... + +bfpbox + +Anthony + + + + +# Cover / Promotional Art - 03/07/2001 by Anthony + +Hey guys. Since my old posts aren't here any longer, I'll remind you newbies who I am. I do stuff like the character select screen drawings, which I hope you all enjoy. Currently I'm working on cover and promotional art for BFP. Note that I am not in any way suggesting that BFP is going to be released outside of freeware on the Net-we just want to have plenty of graphics and titles that can be used to promote the best mod around, both for Phase 1 and the following products. Anyway, since it's all under the blanket until we see release, I won't be able to really describe my progress here-just suffice to say that progress is being made! + + diff --git a/docs/old_dev_journals/chris_dev_journal.md b/docs/old_dev_journals/chris_dev_journal.md new file mode 100644 index 0000000..3b75171 --- /dev/null +++ b/docs/old_dev_journals/chris_dev_journal.md @@ -0,0 +1,582 @@ +# BFP release time - 01/18/2002 by Chris + +We hope to have Bid For Power ready by 6 PM pacific time. + + + +# Still need mirrors - 01/18/2002 by Chris + +Now that Bid For Power has been released we need Mirrors now more then ever. If you have managed to fight through the lines and have gotten your hands on a copy of Bid For Power and would like to host is please email me at chris@bidforpower.com +We are getting some reports that windows XP users are having problems getting the .exe version of Bid For Power to work properly. If this occurs then rename the .exe version to .zip and it should work fine. + + + + +# Hold on... Almost there... - 01/18/2002 by Chris + +Bid For Power will be ready around 10 o'clock eastern time. Our ftp decided that it didnt like the bfp and ate it so we are re-uploading. + +

*update*

+ +We are getting some reports that windows XP users are having problems getting the .exe version of Bid For Power to work properly. If this occurs then rename the .exe version to .zip and it should work fine. + + + + +# Last minute mirrors - 01/18/2002 by Chris + +Hey guys. +Any gaming sites still interested in mirroring Bid For Power please send me an email to chris@bidforpower.com + +Please no tripod or geocities accounts. + +Thanks. + +We are getting some reports that windows XP users are having problems getting the .exe version of Bid For Power to work properly. If this occurs then rename the .exe version to .zip and it should work fine. + + + + +# Mirror, mirror, on the wall... - 01/11/2002 by Chris + +If there are any webmasters who be interested in mirroring Bid For Power please [email me](mailto:chris@gamethrust.com). All that we ask in return is that your site is capable of numbering the amount of downloads. +Thanks. + + + + +# Bid For Power - 12/27/2001 by Chris + +If you want to truly understand something, try to change it. + + + + +# Clouds composed of water vapor are up in the, you know, sky. - 12/17/2001 by Chris + +We're trying to get the last two models animated, skinned, prepped, etc... You name it, we're trying to get it done but progress is slow and delays are very fast. What I can say about the release is that it won't happen before Christmas. +Well now that the bad new is out of the way I can hint at the good news, as to wether we can get it out before New Year's is very much up in the air but the release will be soon. + + + + +# The Monday Update - Chris - 11/26/2001 + +Dash has been revising his Palace level and it's looking amazing. He has been working on improving the lighting, adding different floor textures, creating new details in different areas, and optimizing the building structure to make it run smoother. The screenshots speak for themselves though: + +

+ heaven_03 + heaven_04 +

+ +

+ heaven_05 + heaven_06 +

+ + + + +# The Monday Update - Chris - 11/20/2001 + +Another late Monday's Update but I think this one is worth it as we're showing off six new in-game screenshots and one brand new rendering. The six in-game screenshots show off our three completed characters, Kyah, Gothax, and Shilo all modeled and skinned by Rodney. These screenshots are taken from the new beta the team has been playing around with. The rendering is of, Ryuujin, Pyrofraggors new character he is woking on. + +

+ Ryuujin_InProgress +

+ + +

+ bfp_characters_beta_01 + bfp_characters_beta_02 + bfp_characters_beta_03 +

+ + +

+ bfp_characters_beta_05 + bfp_characters_beta_06 + bfp_characters_beta_07 +

+ +I also thought I'd go ahead and post some of the recent developer journals as they have quite a bit of good updates in them. + +- Yrgol's Developer Journal
+_"What we are currently working on is equivalent to beta 2 for most mods, or in bfp jargon phase 1. We had phase 1 completed for dbz quite a long time ago, and then the funimation/infogrames stuff happened. Most of the time since then has been used for converting bfp to not use dragonball z copyrights._ + +_Direct hit detection has been made more reliable. This makes the kienzan equivalent and the deathball easier to use._ + +_Explosion damage is no longer all or nothing. The damage decreases as distance from the explosion increases like in baseq3._ + +_Attack radius has been separated from explosion radius. The attack radius and explosion radius for each of the attacks have been tweaked._ + +_The knockback physics for attacks and explosions has been seriously tweaked. Attacks can now be used to rocket jump._ + +_Boost jumping has been toned down. It was simply too excessive before._ + +_Another total rebalance of all attacks happened."_ + +- Pyrofragger's Developer Journal +_"Sometime this week i'll have pictures of Ryuujin for you guys. The model is done and uv mapped, but i have to redo much of the physiquing and ansel still has to skin it. You'll see skinned renders in here soon as well as ingame shots on the mainpage."_ + + + + +# beta and a little extra - 11/18/2001 by Chris + +Yrgol uploaded a new beta for the team to test out. i've been playing it for awhile now and it really plays great. The overall balancings (mentioned in Yrgol's journal) really work nice now, the game has an awesome feel to it. +I noticed my comments concerning Nintendo marketing mainly to kiddies didn't go unheard as I got quite a few emails about the comments. The emails I got ranged in intelligence but overall a lot of people are really looking forward to the new resident evil games exclusively on the gamecube. But I still think gamecube is being marketed to kiddies. + +Also, a few journal entries back I mentioned putting up a signed copy of Half-Life, by the team at Valve Software, on [Ebay.com](https://web.archive.org/web/20020210145200/http://www.ebay.com/). Well I finally got around to it and you can now bid on a [first edition of Half-Life signed by the team at Valve Software](https://web.archive.org/web/20020102191742/http://cgi.ebay.com/aw-cgi/eBayISAPI.dll?ViewItem&item=1299439693). + + + + +# The Monday Update... On Friday - Chris - 11/10/2001 + +Sorry for the late update but I didn't get back from the Gamecube party till later then expected. As promised here are the brand new in-game screenshots of our new character Shilo, modeled by Rodney. Shilo has some strong anime influences and it really comes out in his shape, check it out: + +

+ bfp-shilo01 + bfp-shilo02 + bfp-shilo03 +

+ +

+ bfp-shilo04 + bfp-shilo05 + bfp-shilo06 +

+ +Here goes my rant about the Nintendo Gamecube for anyone interested. I had some high hopes for the Gamecube considering Nintendo's track record with it's releases. Usually with its releases it has some good launch titles and really makes good use of the system however the games I played (which were all on High Definition Panasonic Flat Screen TV's) seemed less then great. The graphics were good on the Star Wars game but other games like Luigi's Mansion, Waverace, and Star Fox were kind of bland, it seems like Gamecube is going in the "Kiddy Games" direction. I hope Nintendo starts to get more into realistic graphics, if they dont I could see them singling out a lot of people. + +If anyone knows of any launch parties for the Xbox in the Seattle area please email as I would be very interested in attending. Been dying to play an Xbox. + + + + +# The Monday Friday Update - 11/08/2001 by Chris + +On Friday we will be posting our Monday Update as things got a little hectic on Monday so we pushed it to this Friday. All is not lost as we will be showing off Shilo, the new model by Rodney, in-game. +In a semi-gaming related note, does anyone know if Xbox will be having a release party or anything similiar to Nintendo's Gamecube club where they basicly have parties where everyone can come and play the games? I found out Nintendo is having a Gamecube Club party in Seattle which is only about 20 minutes from where I live so this weekend I will be heading over there to check out some of their games. If anyone else in the seattle area is going as well drop me a line. + + + + +# Mondays update - 11/05/2001 by Chris + +Hi guys. +I'm a little backed up right now but hope to have mondays update later tonight. Until then I suggest you either download the second test version of wolfenstein or try and grab a copy of the star wars teaser though its difficult to find a good site to download it from. + + + + +# Monday Update - Chris - 10/29/2001 + +Some brand new in-game screenshots of three of our new characters; Kyah, Gothax, and Pyrate. The action takes place in the Triblecle Creek, an improved version of Dash's Namek level. Next update will include in-game screenshots of our newest character, Shilo. + +

+ ingame_01 + ingame_02 + ingame_03 +

+ +

+ ingame_04 + ingame_05 + ingame_06 +

+ +I also thought I would repost some info dug out from the recent Developer journal updates as some big stuff was mention; + +Yrgol's Dev Journal: + +- _"Per-skin config files have been added that allow things like attack names and voices to be defined by the skin. I have plans to move a lot of stuff into these config files, including attack shaders, sounds, and models. The individual models will never be able to define how the attacks operate, only how they are displayed to the user. "_ + +- _"I'm working on attack scripting for servers only. This will allow new attacks of any rough type that bfp already contains to be added to the game with little or no code. I have a script file set up and partially implemented that has like 30 variables for each of the attacks. This will not allow models to define new attacks, but it will allow team members to work on attacks without me, or mods of bfp. It also lets server ops who hate the game balance to tweak it significantly. "_ + +Remisser's Dev Journal: + +- _"I compiled the music... if anyone has any clue as to how to make these files smaller, tell me..."_ + +Chris Dev Journal: + +- _"Last time I updated I hinted at reworking our version of capture the flag and I've spent some time working different additions to it. Some new twists to classic game which add a lot more to it."_ + +- _"I spent sometime on designing a single-player/co-op gameplay mode which is definately a fast paced experience very similiar to something like... Dragonball Z. Keeping with our "new release/ new gameplay mode" idea I hope to have this be our first new gameplay mode release after our initial release."_ + + + + +# Gameplay additions - 10/24/2001 by Chris + +Last time I updated I hinted at reworking our version of capture the flag and I've spent some time working different additions to it. Some new twists to classic game which add a lot more to it. +I also spent sometime on designing a single-player/co-op gameplay mode which is definately a fast paced experience very similiar to something like... Dragonball Z. Keeping with our "new release/ new gameplay mode" idea I hope to have this be our first new gameplay mode release after our initial release. + +In other news, I've been thinking about starting an auction on [Ebay.com](https://web.archive.org/web/20020210145200/http://www.ebay.com/) (No, I wont be auctioning Bid For Power) of a first edition copy of Half-life I have which was signed by everyone from the original team at Valve Software. I got rid of all my game boxes and stumbled across Half-life, it's just been sitting on a shelf (unopened) for years now and thought someone else might appreciate it more. + + + + +# The Skies Above Heaven - Chris - 10/23/2001 + +Here are some new shots of the brand new skyline for Dash's Palace level. We will be updating with a new shot of Pyro's character just as soon as he gets everything finished on it, stay tuned. + +

+ palace_01 +

+ +

+ palace_02 +

+ + + + +# Midnight Update - 09/30/2001 by Chris + +It's midnight and I got finished looking through most of the posts on the forum so I thought I'd make an update to the dev journal. In a week from wednesday we will be having our internal due date for all the models going into the first release of Bid For Power (just so I dont get a lot of brainless posts on the forum, no this does not mean we are releasing in a week). +I was playing NilreMK's new model, pyrate, and it's an awesome piece of work, once it's fully skinned and animated we'll be sure to post shots of it on the main page along with Pyro's new model. + +I finally got DSL installed. I heard all those horror stories about having trouble getting the line to work and never thought it would happen to me. Well it did. Everything from having my line dropped, wrong equipment shipped, faulty NIC, secondary line problems, and pissed off tech support people who had me on hold for a total of 4 hours. + +Since I now have the luxury of playing games over the internet without having huge slowdowns I have been addicted to RTCW, especially having sniper only games. + + + + +# Status update - 09/14/2001 by Chris + +We've been working hard on getting the new Bid For Power ready and as Yrgol mentioned earlier our biggest competition is from ourselves. The new modelers in my opinion have outclassed our previous work that really shows the talent of our modelers. +As far as a release date is concerned were working hard to get everything ready but due to several things we haven’t been able to name a date nor will we till we are absolute. Apologizes if any previous updates had people thinking otherwise but I guess that’s the way it is when developing games. + +Our site has become riddled with advertisements which unless we’re in nascar don’t go together. We will likely be starting up a donation box similar to what planetnamek had which if we are successful we can remove all the advertisements (except for the banners at the top). + +Also, Computer Gaming Monthly ran a blurb about Bid For Power and I have been unable to find a copy of the magazine to check it out myself. If anyone has a copy of the article and can scan it and send it to me at chris@gamethrust.com it would be a big help. + +Thanks. + + + + +# Announcement - 08/05/2001 by Chris + +Expect the announcement to go up within the next hour. Thanks for your patience. + + + +# Updated features - 07/17/2001 by Chris + +Some new things to mention. +We've created some new Attack icons which look a lot more realistic then the current set of attack icons. Ansel has been working on this new set and they look great, here is an early version of the Kamehameha: + +

+ kamehameha_icon +

+ +Another thing to mention is the new interface we are planning. With the release of TA's code we can now implement a more advanced UI into Bid For Power. We're still working on the different looks but hopefully we can have something more solid to show next update. + +In Yrgol's previous update he mentioned the custom model support. We decided to branch out with this and add several different versions of Vegeta. Now instead of choosing between different skins you can now select different models of Vegeta. Here is what we have planned. + +Vegeta from the Freeza Saga +Vegeta from the Cell Saga +Vegeta from the Buu Saga + +Each model will range in size and shape and with three different modelers they will each have their own unique look. I should also note that we will still be keeping the existing set of skins for the models. + +Should have some good things to show off in the next update. + + + + +# Android #16 mirrors - 06/20/2001 by Chris + +We are low on mirror sites for Android #16 so if you have a site that is willing to host Android #16 please [email me](mailto:chris@gamethrust.com). + + + +# Life insurance pays off triple if you die on a business flight. - 06/18/2001 by Chris + +The site laucnh is going great and now we've pretty much got everything up and running as the image section will be ready soon enough. The dev journals, team section, faq, about, etc... are all doing great and if they haven't been updated yet will be later tonight. Android #16 should be ready for download tomorrow. We're packing him up right now and then sending him off to the good folks at fileplanet to be mirrored. The FAQ section will get a nice little update over the night and if you guys have any suggestions for new questions please post them in our [forum](https://web.archive.org/web/20020210145200/http://www.bidforpower.com/forum/showthread.php?s=83d20c406900acb34d0a60f904548094&threadid=6049). Expect some even cooler looking screenshots for tomorrow's update. + + + +# The martyrdom of Saint Me - 05/29/2001 by Chris + +Much has been happening but due to the new site we have been laying off on updating which is mainly my fault. It has been a busy couple of weeks and since the shadow of E3 has past us we can start to kick it into high gear again. +The ETA on the new site is still up in the air but the good news is that when we release the new site we will also release a playable Android #16 for Quake 3 Arena much like we did with our Vegeta model. + +We should have some more news soon. As far as other stuff I finally met Chuck Palahniuk at a recent reading for his new book, Choke. He is a very cool guy and now I will stop quoting Fight Club and start quoting other work by him. + + + + +# Where you going with this ikea boy? - 04/27/2001 by Chris + +Tomorrow I will finish up work on the in-game footage and should have something fairly nice to show off though it will take a little while for Fileplanet to begin mirroring the clips so they may not be officially posted till monday but they will be worth the wait. +Some sites have been emailing me asking if they can have some type of banner to use to link Bid For Power from their sites. About time I posted this banner, we should have a button available soon as well. + +

+ + bfp-banner01 + +

+ + + +# insert Fight Club saying here - 04/23/2001 by Chris + +I am doing a little video work and I was wondering if anyone here knows of a good program to extract both audio and video from quake 3. It is very important it records both audio and video, if you know where I can download a copy of a program like that please email me at chris@gamethrust.com +thanks. + + + + +# I am Jacks smirking revenge - 04/15/2001 by Chris + +The link submission is over as I collected way too many then I expected but thanks to everyone who submitted. We cant add everyone and most likely we will only add about six links. As for the ETA on the new site it will be about 2 weeks as we are waiting for some help from gamethrust to convert the site over to php3 format. +As you have noticed from some of the other members subtle hints work is closing on phase I. + +Anthony finished up work on the boxart for Bid For Power and it looks amazing, we are very eager to show it off. Very cool stuff. + +I managed to find some time over easter sunday to work on some levels for Half-life, take a peak: + + +

+ visions110000 +

+ +[Some other levels of mine](https://web.archive.org/web/20020215120703/http://www.bidforpower.com/chris/portfolio.htm) + + + + +# Link Submission - 04/13/2001 by Chris + +I am working on the link section to Bid For Power. If you are interested in having your site added please email me at chris@gamethrust.com Please no clan sites. +Please note that we are looking for sites that are either DBZ oriented, Quake 3 oriented, or Bid For Power oriented and not all that are submitted will be linked. + +As well as I am on the subject of updating the site, if there are any questions you think should be added to the FAQ email them to me and I will try and include them. + +And yes the new site will have a working image section... + + + + +# New Model Clips - 04/09/2001 by Chris + +The clips were hurting our bandwidth so I went and moved them to a new server. They will be posted on the main site in a little bit. +Just wanted to thank everyone that emailed me about mirroring the files. + + + + +# I am Jacks Late Fee - 03/26/2001 by Chris + +Looks like RC 3 did not go as planned. We will have it out the door by the end of the week. As for where our models stand the buff version of Freeza has been modeled and exported and now Rodney is working on the skin for him. +Pyro finished the models of gohan and krillin on sunday morning so Ansel is working on skinning both Krillin and Gohan. In case you were wondering krillin and gohan are modeled with their original Gi outfits. + +Later this week I can update with the body transformation of Freeza and the new models of Krillin and Gohan... who knows, maybe even an in-game movie. + + + + +# I am Jacks missing Skin Artist - 03/24/2001 by Chris + +Hi guys. +We are trying very hard to meet the monday deadline for RC III but we have run into a slight problem. We have three models which need to be skinned and currently our skin artists already have work or are discovering a life away from the computer for the weekend (could you blame them?) + +Being that we would really like to meet the monday deadline I am asking if any skin artist would like to give some temporary assistance and help with the skinning of our Krillin and Freeza models. + +The skin artist would have to be very good and have some prior experience to show plus he would have to work under the gun as we have about two days to get the skin done. + +If anyone would like to volunteer to skin our Krillin and Freeza models please email me at Chris@gamethrust.com with a link to some of your prior work. + +Thanks + + + + +# I am jacks update - 03/23/2001 by Chris + +Well I know the website hasnt been updated in awhile and thats partly my fault but dont worry we have kept ourselves busy. +Yrgol is working on a slight rebalance to phase I damages. Pyro has Krillin and Gohan done and Ansel should have the skins done this weekend meaning monday we will have a new gohan and krillin in game. + +NilreMK is working on exporting Tabouhs Freeza model(s) which should also be finished on monday, meaning monday will hopefully be a nice update of Gohan, Krillin, Freeza and also Kits new map and as a bonus Dashs updated namek waters. + + + + +# Without pain, without sacrifice, we would have nothing. - 03/09/2001 by Chris + +I would like to announce that for Phase II of Bid For Power (which will chronicle the android and cell sagas) we will be introducing an advanced melee system, rebalanced attack system, and several new RPG elements including "Unique Characteristic Traits" which will give each character their own special abilties. +An example of this will be that Piccolo will have the ability to regenerate health and Vegeta will have the ability to increase his power after experiencing an attack. Its getting pretty late so I will likely update tomorrow with a much more thurough explaination. + +Stay tuned... + + + + +# With a gun barrel stuck between your teeth, you only speak in vowels. - 03/08/2001 by Chris + +Announcement(s) postponed till tomorrow... + + + +# I am Jacks raging bile duct. - 03/07/2001 by Chris + + +Hi guys, +Sorry that I havent produced that video clip yet but I was holding off until I setup my new system. I did some talking with Yrgol and we are going to be adding even more effects to the power struggle so once those effects are in and my system is up to par I will post the video. + +Now on to even better news... Phase II news. We consider phase I our test run, our basic platform to build on. We are now planning some of the various new features for phase II and we may be adding a ton of new features including advanced melee system, new tier system, and an RPG edge to it all, very cool stuff. + +Tomorrow I should have some more interesting news about our phase II plans and maybe even some bigger news, all depends on how things work out. + +Almost forgot, I thought I would list some of the Bid For Power fan sites out there for people to check out: + +[Bid For Power World](https://web.archive.org/web/20020210145200/http://bfpworld.digitalnova.com/) + +[Bid For Power Online](https://web.archive.org/web/20010609020639/http://bfponline.4t.com/news.html) + +[PlanetBFP](https://web.archive.org/web/20010515205855/http://www.planetbfp.com/) + +[Bid For Power Clan League](https://web.archive.org/web/20010516202746/http://www.bfpcl.f2s.com/) + +[Bid For Power Net](https://web.archive.org/web/20020120230500/http://bfpnet.cjb.net/) + + + + +# I am Jacks cold sweat. - 02/19/2001 by Chris + +I have been tinkering on a new 30 second clip showing off a fight between some of the characters and unfortunately the movie of Piccolo fighting Krillin in the new Cell Tournament map feel to the cutting room floor but I thought I would atleast go ahead and post some screenshots of the now deceased video. +If I were to post a movie clip showing off a battle between two opponents who would you want the movie to have in it and at which map? [Post your answer in our forum](https://web.archive.org/web/20010407130034/http://www.bidforpower.com/forum/showthread.php?threadid=18016) + +

+ pic_vs_kril_01 +

+ +

+ pic_vs_kril_02 + pic_vs_kril_03 +

+ +

+ pic_vs_kril_04 + pic_vs_kril_05 +

+ + + + +# I am Jacks complete lack of surprise. - 02/18/2001 by Chris + +I was supposed to update with some screenshots of Cells Tournament but I decided to take it one step further and update with a vide clip from the map. I decided to create a clip of Gohan performing his Angry Gohan Attack but unfortunately the compression sucks and the 25 second clip runs out to be 17 megs. +And with our server I dont think we will be able to handle it so most likely by tonight I will be updating with a very compressed smaller version of the AGA. + + + + +# You have a kind of sick desperation in your laugh... - 02/13/2001 by Chris + +Gamethrust has posted a preview of Bid For Power :: RC 2. This is the first preview on RC 2 and most likely will be the only online preview of it and I suggest you all visit [Gamethrusts Preview of RC 2](https://web.archive.org/web/20010406214208/http://www.gamethrust.com/?page=game_preview&gameid=bidforpowerpc) . +We have some big plans for RC 2 but I wont be able to comment on those for atleast a few days until then though I suggest you call check out [Gamethrusts Preview of RC 2](https://web.archive.org/web/20010406214208/http://www.gamethrust.com/?page=game_preview&gameid=bidforpowerpc) . + + + + +# The things you own end up owning you. - 02/04/2001 by Chris + +Just a quick update before I start heavily updating the site over the weekday. I stumbled across this website and it really impressed me. The site is called [BidForPowerWorld](https://web.archive.org/web/20011202104243/http://bfpworld.n3.net/) and looks great. +From what I hear they are in need of some additional staff members so if you are interested I suggest you send them an email. + + + + +# Is Tyler my bad dream? Or am I Tylers? - 01/27/2001 by Chris + +I just thought I would update and tell everyone that both Planetquake and Quake3world have posted previews of Bid For Power +[Quake3worlds Bid For Power Preview](https://web.archive.org/web/20011224224432/http://www.quake3world.com/modifications/bfp.html) + +[Planetquakes Bid For Power Preview](https://web.archive.org/web/20020221094152/http://www.planetquake.com/features/articles/bidforpower_a.shtml) + +A big thanks to both those sites for doing the previews. In case your wondering if any other sites will be doing previews of Bid For Power in the near future the answer is yes. The GNN network will be doing a preview of Bid For Power and it should be ready soon. + +Since everyone on the forums have been wanting to see more shots of our newer maps I will be posting screenshots of each map on our site over the next week. + + + + +# Updates soon enough - 01/20/2001 by Chris + +We have been hard at work on Bid For Power and soon enough I will be updating the website with several shots from our new maps which include Northern Earth, Glacier Lands, The Cave, NamekXL, and the Cell Tournament map. +As far as news on models the standard animations for Krillin and Gohan have been completely redone so they play a lot more smoothly now. We are about to shift work to Phase II in the next week on our models. + +I will also be updating the site with some roll-over clips of all of our models and I will also post a video clip of our Oozaru model which is fully modeled and skinned. + +The big updates should start on monday. + + + + +# Hey its working again - 01/07/2001 by Chris + +As you can tell our Developer Journals are working once again. They were down quite awhile but now everything seems to be under control. +First off, as you can tell by Planetnameks preview we have a beta copy of Bid For Power floating around. You can expect another preview soon by a Quake 3 oriented website. +This beta was made only for websites to preview Bid For Power and we will not be releasing it publicly. There is a lot more to talk about but I'm a little tired right now so I need to get some sleep. + + + + +# Emails and the almighty FAQ - 12/16/2000 by Chris + +I just wanted to let everyone know before they decide to email me or post on the messageboard about a specific question they should check out our FAQ and About sections, they have tons of information and 9 times out of 10 they have the answers to what your looking for. +I get a lot of people emailing me asking about when bid for power will be released. The answer is and always has been "when its done" emailing me wont change this. + +Check out the FAQ and Abour section as they are a great resource of Bid For Power information. + + + + +# Dev Journal Shift and some video clips - 12/14/2000 by Chris + +Recently Eddie did some tweaking on the developer journals so now only the five most recent journals are displayed on the main section while the others are displayed in the Dev journal section. +It looks like I am going to have to push back the release of the in-game clip a little because we had some lower framerates when recording as well as a huge download. + +I will be working over the weekend of splicing together some cool action sequences from in-game and hopefully posting it sunday night. + + + + +# FYI - 12/09/2000 by Chris + +I just updated the site and I just wanted to let everyone know that the vegeta and freeza featured in that update are not finished. +I have an out of date version of vegeta so his skin is not the final version you will see in Bid For Power though if you check out the last image of vegeta at the bottom (in the pepper city map) you will see his final version. + +Last weekend Rodney finished up the animations and tweaks to our freeza model which is now a lot larger looking but the skin is not in its final form. It will be soon though. + +Should have a lot more updates coming soon. + + + + +# Website Stuff - 11/29/2000 by Chris + +It seems the Developer Journals are now displaying properly. It's time for me to do another "State of the BFP” update. +As you can see we moved servers and the reason for this is because the popularity of Bid For Power is increasing and our previous server was getting hit pretty hard so Gamethrust offered to host us on there server which is a lot faster. I just want to thank Eddie at Gamethrust who has worked his ass off programming the site and setting it up and without Eddie we wouldn’t be at this new server, so big thanks to Eddie. + +There are still one or two areas of the site that are offline but they will be online soon enough. Our forums are flowing with tons of activity and we have recently been leaking some really nice screenshots on to the forums so if you haven’t checked them out yet do so or your missing out on the eye candy. + +Right now some of the team is focusing on Battle For The Planet and incorporating both Brolly and Oozaru to the game play. As for whether or not Brolly will make it into the Phase I release of Bid For Power is still unclear, we should know by the end of the week. + +I’d also like to point out one of the cool little things of Bid For Power that has seemed to go unnoticed so far. In Bid For Power we allow the user to setup the visuals to his or her liking and by doing so we give the player a lot of options. For example our Auras, we allow the player to select between four different versions, this feature applies to a lot of the visuals in Bid For Power and I think the fans will be fairly impressed with it when Bid For Power is released. + +The past few updates are what I call “tech” updates which aren’t meant to show off game play as much as they are to show off various effects and features we have put into the current builds. I will be posting a few more tech updates followed by some in-game video clips (still bordering on tech updates) and from there hopefully I will post some game play shots. + + + + +# Website Craziness - 11/29/2000 by Chris + +As you can tell the website is a little off right now while we are implmenting the developer journals. Sorry for the problems but they should be fixed by the end of the night. diff --git a/docs/old_dev_journals/dash_dev_journal.md b/docs/old_dev_journals/dash_dev_journal.md new file mode 100644 index 0000000..49be83c --- /dev/null +++ b/docs/old_dev_journals/dash_dev_journal.md @@ -0,0 +1,256 @@ +# Wallpaper - 01/06/2002 by Dash + +I'm making some art, and using **Rodney**'s renders and one of **Ansel**'s Ryuujin draws, I've did this wallpaper for you guys. + +bfp_wallpaper + + + + +# The Rainbow under the Waterfall - 12/30/2001 by Dash + +Today I did some work on the Mystic Mountains Map; deathmatch version. Here are some new screenshots, featuring a Rainbow, generated by the waterfall. Enjoy + +mm_new3 + +mm_new1 + +mm_new2 + +mm_new4 + + + + +# Mystic Mountains New Pics - 12/29/2001 by Dash + +Here are some new pics of **Mystic Mountains**. I finished the terrain, I added some trees ( by Pyrofragger ), but Kit Carson will add the other details and will do the entity work. Anyways it will be ready to be included in BFP release. + +mm_2 + +mm_1 + + + +(old lost image: https://web.archive.org/web/20020611021434/http://bidforpower.com/dash/mm_3.jpg) + + + + +# Mystic Mountains - 12/28/2001 by Dash + +**Mystic Mountains**: this is the name of the **BFP CTF** map that me and kit are going to work on in the last days before the release. **Pyrofragger** is doing awesome trees models for it, and when they will be in there will be a lot of scale sense. I've updated the **POTW** with a screenshot of the waterfall in the map, wich isn't the final, but it show up a bit of its effect. + + + + +# CTF Maps - 12/26/2001 by Dash + +Now that I have some things to show I can say you Merry Christmas. Me and Pyrofragger yesterday have started to think a Bid For Power CTF map and I have already finished the Terrain for it. Just to tell you, this map can contain 20 times Tribecle Creek (Namek Waters) or more. Thanks to Kit Carson for his sky. Wait some days for the completed one (With Giant Waterfalls). By the way, I'm creating the terrain for another CTF map, and on this one me and Kit will do a Team work. +Here are some pics of the terrain. + + +dactf1 + +dactf2 + +dactf3 + + + + +# Planet YH-85 Finished - 12/16/2001 by Dash + +I finished my new map: **Planet YH-85**. Here's a screenshots, featuring **Pyrofragger's** awesome Meteor model. + +yh85_final + + + + +# Planet YH-85 | New BFP Map - 12/15/2001 by Dash + +Ok that's the story... I'm creating an Urban Terror map, and I created a Terrain for that. I decided to use that terrain, with a different lighting, and different things, to create a map for Bid for Power. The map is now almost complete, I think I'll add a Meteor map object in the crater, if **Pyrofragger** create it for this map. It's name is **Planet YH-85** a desert, very cold and ostile planet, in the Eastern Galaxy. +Yes, you can see well, you don't need goggles. It's the great Vegeta, and I'm using the Dragon Ball Z Fanpack with the new BFP Beta Test. +Here are some screenshots: + + +yh85_1 + + +yh85_2 + + + + +# Update - 12/14/2001 by Dash + +Just to update my Dev-J, here are 3 screenshots of **The Palace** level. Chris hasn't posted these on the main page, so I'm going to post'em here. And about the **Christmas Header**, yes I did it :) +Booyakayshaa ! + + +palace1 + +palace2 + +palace3 + + + + +# Dash Design Redesigned ! - 11/28/2001 by Dash + +I've redesigned Dash Design Go to check it ! There are some cool things to see... +Thanks + + + + +# What the new POTW is ? - 11/12/2001 by Dash + +Well, today I was working on a Gundam Universe map using EasyGen. But, I said, why don't I make a terrain to use into a Bid For Power map too ? +**A Rethorical Question.** + +So I did this thing using Tribecle Creek (Namek Waters) Textures, and some from Kit's NamekXL (The Ground). + +I have yet to add some trees of curse and more details. So I dunno if I'll finish it in time for the imminent relase of the game. + +Visit Dash Design !
+**WWW.PLANETQUAKE.COM/DASH** + + + + +# Wop, I'm back - 09/05/2001 by Dash + +I'm back and I've already made some new things for bfp... the new header of the site for instance. But, I have almost done the changing of the name of all the maps, and some are going to change a bit (lighting for instance) Now Im going to reveal the new names of the maps so pay attention: +**Kami's Lookout** will change into **The Palace** +**Namek Waters** will change into **A Strange Planet** + +**Abandoned Southern City** and **Northern Earth** will keep their names of curse. + +About [Dash Design](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash) my personal site, I will redesign it soon (I have the graphic done but I'm still converting the contenent) + +So stay tuned for updates. + + + + +# Update - 08/15/2001 by Dash + +Bah, that's all I have to say about the event wich are happening. By the way, I will by busy untill the 1st of september, because of internet connection problems that won't be resolved till that date. ( So don't expect to see me a lot online ) +And today it's my birthday, the 15th of August. Happy Birthday Dash ! + + + + +# Dacity Finished - 07/19/2001 by Dash + +Here are 2 screenshots of my Abandoned Southern City. Final Version. I hope to have some Deepest Canyon screenshots ready soon. + +dacityshot0004 + +dacityshot0006 + +[[**Dash Design**](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash)] + + + + +# The Deepest Canyon and other stuff... - 07/08/2001 by Dash + +Just a quick update today: here's a draw of my latest map The Deepest Canyon This map will be quite huge and eyecandy and it will contains those func_breakable entities from Yrgol. Stay tuned ! + +dacanyon + +[[**Dash Design**](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash)] + +Also Bid For Power Zone has got an interview of me. Check it out [here](https://web.archive.org/web/20020223210411/http://www.bfpzone.f2s.com/dash.shtml). + + + + +# Namek Waters Final3 - 06/28/2001 by Dash + +Namek Waters is finished, all the small bugs are fixed. Check [these](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash/mystuff/namekwaters3/page_01.htm) screenshots, showing in-game Goku and some cool things. +Note: Namek Waters Final3 is the same of Final2, only optimized. + +[[**Dash Design**](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash)] + + + + +# Kami's Fixed and other things - 06/26/2001 by Dash + +Here's a shot of the latest build of my Kami's Lookout map. Look at the new palms. + +finalkami + +Here are in detail the things I've added and fixed in these days: + + +**::Namek Waters FINAL3 Version:** + +- Added a Geiser +- New Waterfall Texture, +- New Particle Effect for the waterfalls' splashes, +- New Lighting, not overbrighted, +- Gridsize changed from 256 256 256 to 512 512 512. + +**::Kami's Lookout FINAL5 Version:** + +- New texture for the tiles outside, less repetitive and more small +like in the show, +- New Lighting, not overbrighted, +- Added a red carpet inside. +- New Palms, +- Sounds when you hit the palms + +[[**Dash Design**](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash)] + + + + +# Namek Waters 2nd Edition - 06/24/2001 by Dash + +Today I've compiled 2 maps. One is Dover Base for Gundam Universe and one is... **Namek Waters** -Super Special Edition. Here are some screenshots... Enjoy +[Click Here For The PICS](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash/mystuff/namekwaters2/page_01.htm) + + +[[**Dash Design**](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash)] + + + + +# Water Effects - 06/23/2001 by Dash + +It's time for the first update of Namek Waters. Here are my new effects for the water (waterfalls, sprays, geisers ). These shots are WITHOUT lighting, so expect tomorrow another update, showing the new nice lighting too. + +watereffect1 + +watereffect2 + + +Wait till tomorrow for a new update... [[**Dash Design**](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash)] + + + + +# Work that I've done and work in progress... - 06/22/2001 by Dash + +Wow, my first update in my super cool dev-journal. Here it is what I've done for Bid for Power. +- Northern Earth, +- Namek Waters, +- Kami's Lookout, +- Abandoned Southern City, +- All the textures for the maps above, +- Some minor things. + +Now I'm going to fix all my works. Kami's Lookout is finished and fixed, Northern Earth too. I'm going to recompile Namek Waters with a more contrast lighting and maybe make some fixes in Abandoned City. +Here are some nice in-game screenshots in the city: + +[Click Here](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash/mystuff/city1scr/page_01.htm) + +I'll upate with screenshots of the updated Namek Waters when I have it ready (Soon). HEY ! Don't forget to check my personal web page: + +[[**Dash Design**](https://web.archive.org/web/20020223210411/http://www.planetquake.com/dash)] diff --git a/docs/pyrofragger_dev_journal.md b/docs/old_dev_journals/pyrofragger_dev_journal.md similarity index 96% rename from docs/pyrofragger_dev_journal.md rename to docs/old_dev_journals/pyrofragger_dev_journal.md index 90efbfa..6cee84a 100644 --- a/docs/pyrofragger_dev_journal.md +++ b/docs/old_dev_journals/pyrofragger_dev_journal.md @@ -1,727 +1,728 @@ -# Meteor - 12/16/2001 by PyroFragger - -I spent a couple hours today helping dash with his new map by creating this meteor map object that rests in the crater. - -coolmeteor - -It looks 10x as cool ingame. - - - - - - -# Hehe, i bet you thought i forgot:) - 12/13/2001 by PyroFragger - - -Trying to be a little more faithful to my promises, spent the last 4 hours working on this: - -TetRender - -Excuse the slow load times of the bfp server. This is Tetsedah, one of the bfp character drawing contest winners. I sat down and reconstructed the mesh using my basemodel instead of Rodney's. Here's a picture of the sketch that Jeff aka Dark Cloud drew: - -contest3 - -Also, that awkward shadow on the collar of the jacket is just a rendering artifact, no idea what causes this but it's not part of the model so don't worry. - - - - - -# Arggg - 12/12/2001 by PyroFragger - - -My computer crashed and i lost some tweaking work i had done to the model. Not a problem, but i have to postpone the shots till tomorrow. Hopefully this won't happen once i have more RAM. Home stretch:) I aim to have both of the models ingame with temporary solid color skins by the end of this weekend, hopefully the skins will be done really soon. -/me hands Ansel more gatorade - -Go Go Go! - - - - - - -# Go baby Go - 12/12/2001 by PyroFragger - - -I can have Ryuujin INGAME the day after i get the skin from Ansel. In the mean time, i work on the second model. It\'s close to done, i\'ll show it off later tonight around 10 oclock pm EST. The finish line is in sight, cheer ansel on! -/me hands Ansel a gatorade - - - - - - - -# Ryuujin Model Finished - 12/04/2001 by PyroFragger - - -Finished all the stuff i need to do for Ryuujin and last weekend sent the maps to Ansel for skinning. Here's a pic: - -RyuujinRender - -It was gonna be in the monday update but there was no monday update so i'm posting it here. - - - - - - - -# Information - 11/22/2001 by PyroFragger - - -This isn't an eyecandy update, just an informational one. As yrgol said, we can release BFP around a week after he has all the models completed. I have ryuujin completed and ready to go ingame as soon as i get a skin from ansel (who's on vacation at the moment) and will have my second character completed sometime next week after i get another concept from ansel. By that time Pyrate will most likely be ingame, so we should be seeing a release very shortly. In my next update i'll have snazzy posed renders of Ryuujin and Komasurao, not saying when though because there's too much that can happen that can delay a date. - - - - - - -# Ryuujin Coming Soon - 11/17/2001 by PyroFragger - - -Sometime this week i'll have pictures of Ryuujin for you guys. The model is done and uv mapped, but i have to redo much of the physiquing and ansel still has to skin it. You'll see skinned renders in here soon as well as ingame shots on the mainpage. - - - - - - -# BaseModel Version 2.0 - 11/13/2001 by PyroFragger - - -For those who care, i reuploaded the BaseModel zip with fixed weighting on the ass. It was collapsing during some of the animations, fixed that right up. Version 2.0, same link as below. -Also, the biped is HIDDEN, not missing. Just go to the fifth tab and unhide all. - - - - - - - -# The Basemodel - 11/12/2001 by PyroFragger - -Here it is. It's fully UV Mapped and Physiqued , with all the tags correctly parented to the biped. For those of you without MAX, there is also the 3ds version that is simply uv mapped, you'll have to attach it to a skeleton in whatever program you use and parent the tags aswell. The uv mapping is done in such a way that all you need is ONE texture for the entire upper and lower body. The head has its own mapping (the head mapping is very very messy but it gets the job done, fix it if you feel like it). I recommend 1 128x128 texture for the head and a 512x512 for the body. -*!*!* UPDATE *!*!*!*! I've ported it to MAX 4. If you've downloaded it already and use MAX4, redownload now! - -Download It Here (broken link): https://web.archive.org/web/20011218203246/http://www.bidforpower.com/Pyro/BaseModel.zip - -Enjoy. Remember, ALL that i ask is for a little credit, and please, if you're going to edit it, edit it enough that its a new character, don't just change his hair to give him a mohawk and release it. I want to see quality character models made from this. - -EDIT: Also, if (for whatever reason) you've seen any bfp skins at all, you'll notice that the uv mapping is set up in the same way as Rodney's. Again, this is NOT Rodney's basemodel, you can check the uv wireframes as well as the actual geometry yourself if you're skeptical. - - - - - - - -# Homestead blows - 11/10/2001 by PyroFragger - - -Seems every time i use homestead to show pictures to you guys i exceed my monthly bandwidth in only one day, lol. If anyone has another free hosting site that allows img tagging (i.e. NOT GEOCITIES!), tell me. - - - - - - -# BAMMMMMMMMMM! - 11/09/2001 by PyroFragger - - -Heh, if you live in another timezone, sue me, its still 11 oclock over here. I still have some work to do on the model but i figure i'll show you what i have today. This is about 6 and a half hours worth of work: - -(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base1.jpg) - -(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base2.jpg) - -(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base3.jpg) - -(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base4.jpg) - -This isn't really Komosurao, just threw his head on the basemodel i'll be releasing later on (most likely tomorrow, i spent more time on the model than i thought i would, but it payed off). The more experienced MAX users on the editing forum should find this useful as either a guide or as a base for their own models. Enjoy. - - - - - - - - -# On Track - 11/08/2001 by PyroFragger - - -All's in order for tomorrow, tomorrow i will be releasing the following to the budding designers in our community: -A MAX file containing my new basemodel i am using to create my bfp characters. The model will be uv mapped and physiqued to a standard bfp skeleton - -A copy of the bfp animation list and an explanation for all of them - -I'll also have pics of my actual bfp models. - - - - - - - -# Friday Update - 11/06/2001 by PyroFragger - -Thank God i'm through with all this work, i'm home free for a bit. Look for an update friday with new stuff. - - - - - - -# Info - 11/01/2001 by PyroFragger - -I don't really spend much time here anymore obviously, mostly because schoolwork this year really picked up. As it stands, i have two exams tomorrow, and two large reports due tuesday and wednesday, followed by some more exams. I'd say after next week, i'll be in the clear for a bit, but until then its tough to say how much i'll be able to do. I MAY be able to squeeze a couple hours of work into Saturday. What i've decided to do is make my own base model for all my characters. Until now, i'd been using Rodney's because it had been determined that the new bfp would just be different skins and head models on the same body. Well we've obviously strayed away from that to give the finished product more variety, so i'd like to make my own basemodel instead of simply just edit other people's work. The same head models will stay though, since those are mine. Once my basemodel is done i'll be able to more easily invent new characters off of it, and i will release the finished, uvmapped, and physiqued basemodel to developers in the community in the form of a bfpsdk to base their own custom bfp models on, provided that in the readme you mention the basemodel came from me. -Most of you have probably already read yrgol's dev journal. That's exactly what i'll be doing with all of my custom models in the future. As he said, there's no way it will be in the first release (which is why i'll only be releasing one of my models for the first release), but eventually all models will have visually different attacks and auras. - -Here's some information on the characters that will eventually be in a future release of bfp: - -Komasurao - Decided to make him evil. He has a very dark, cruel energy. He's a very deceptive and ingenious fighter, he can trick other fighters with illusions and his attacks pack a punch. - -Ryuujin - Stands for Dragon God in japanese. He is, obviously, a god who has spent the past 3 millenia training his mind and his body. He rarely speaks, and is rarely seen, but he always shows up when there is trouble. His ki is "different," kind of mysterious and very powerful. He wields two scerpent-dragon swords whose blades are made of the same ki as a kienzan. - -Tetsedha - A very determined and confident, but not overconfident, fighter. He lives for the fight and trains continously. His ki is very cold and his attacks are slower than those of Taipan. He takes fighting very seriously and is easily a match for anyone. - -Taipan - A very aggressive, firey fighter who is easily ticked off and a force to be reckoned with. He is the rival of Tetsedha, and also lives for the fight. The only thing that keeps him going is the continous struggle to become better than him. This competition has made him a very talented and dangerous fighter. His ki is very firey and his attacks are quick. - -Drexx - As a teenager, he was very violent and irritable, so he was taken in by a master to try and turn his anger and rage into controllable energy. After 18 years of training, he has become one of the most elite fighters in the universe. He possesses a very electric ki, and he is scared of no one. - -Obviously i'm not the most reliable person in the world right now, so i can't make a prediction on when these will all be done, but i can they'll be done soon. I'll try and squeeze in time here and there inbetween working sessions. - - - - - - - -# ........ - 10/20/2001 by PyroFragger - - -Well i'm getting over this motivational slump i've been in for the past month. A lot of things been going on where i just either couldn't find the time for BFP, or just didn't feel like working on it. For those that have seen office space, you know what im talking about. There was about a 3 week period where i just didn't care. Anyways, i figure its best to just knock this out and get it over with. - - - - - - -# Stay away from teh light!!!111111 - 10/12/2001 by PyroFragger - - -Lol............... -The long story is: I was drinking my tea and eating my crumpets when gooey purple aliens teleported into my room. I was like, WTF? and they said: "Come with us, we will teach you many alien bondage techniques." I forget how it ends. - -The short story is: I lost my homestead account and was too tired to reg a new account. - - - - - - - -# LOL - 10/11/2001 by PyroFragger - - -Heh, got so busy yesterday completely forgot about yesterday's update. Anyways, later tonight, unless i forget again (j/k, i wont). - - - - - - -# BOOJAH! - 10/08/2001 by PyroFragger - - -K, im going to show you EVERYTHING ive done so far on wednesday. Unfortunately the contest winners and another 2 models i had going will not go into the initial release of BFP, i'm only allowed to have 1 go in as of now, due to the attack system. However, they will become available in a release sometime after the first release, sorry about that, but its out of my hands. So it'll either by the one i've already pimped on the forums or another model i made that you haven't seen yet, but none of the contest entries will go into the first release, not fair to the other winners. Anyways, check back on wednesday, i'll have some cool stuff. - - - - - - -# Yadda Yadda Yadda - 09/22/2001 by PyroFragger - - -No pics yet, sorry about that but if you've been following this project long enough you're probably used to it:) Anyways, other things have been occupying my time lately and i just haven't had much time/energy to sit there and work on the bfp models. We do have a deadline though, although i cant say what that is, and we are working towards it so the light at the end of the tunnel is within reach. Whenever i have pics ill show em here, not gonna say when because i dont like breaking promises and it'll be cooler as a surprise. --P - - - - - - - -# New Characters, Sneak Peek - 09/14/2001 by PyroFragger - - -These aren't finished, but i'm a little too burned out right now to continue modeling tonight, so for now you can see the bodies of the two male winners of the drawing contest. Tomorrow, i'll have the female winner as well as all three head models on, and sometime this upcoming week i'll have snazzy new renders of these three and 2 others of my own creation:) K, nuff stalling: - -(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Taipan_body.jpg) - -(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Tetsdeha_body.jpg) - -Enjoy. - - - - - - - -# The Winners!!!! - 09/07/2001 by PyroFragger - -Finally, I have sifted through all the contest winners, and here they are. First of all, let me thank everyone who set time aside for this contest. As an artist myself i know sometimes when you enter contests and don't win, it can be depressing, but I saw a lot of impressive drawings in that thread, a lot of you guys have a lot of talent, so keep at it. Without further adieu, here are the entries that made the first cut: - -- DR - page 1 http://darkbostudio.hypermart.net/gallery/darkrapsmall.jpg - -- Kyuusei - page 3 http://www.bidforpower.com/forum/attachment.php?s=&postid;=319192 - -- Kaku - page 3 http://yosefselim.homestead.com/files/Kaku.jpg - -- MITSUKAI - page 4 http://www.bidforpower.com/forum/attachment.php?s=&postid;=319373 - -- Gotrun - Page 8 http://www.bidforpower.com/forum/attachment.php?s=&postid;=321186 - -- God's Entry - Page 8 http://www.bidforpower.com/forum/attachment.php?s=&postid;=321522 - -- Zone - Page 8 http://zone-archive.com/temporary/zonebfp.jpg - -- Adult Chibi - Page 9 http://www.bidforpower.com/forum/attachment.php?s=&postid;=322672 - -- Asura - Page 10 http://www.bidforpower.com/forum/attachment.php?s=&postid;=323126 - -- Cpider's Entry - Page 11 http://www.bidforpower.com/forum/attachment.php?s=&postid;=323770 - -- SSJ-5-Dan's Entry - Page 11 http://www.bidforpower.com/forum/attachment.php?s=&postid;=323994 - -- Yutaka - Page 11 http://danznation.netfirms.com/scans/yutaka1.jpg - -- SSJ-5-Dan's Entry - Page 12 http://www.bidforpower.com/forum/attachment.php?s=&postid;=324903 - -- Taipan - Page 13 http://www.bidforpower.com/forum/attachment.php?s=&postid;=325435 - -- MutilatedPuppet's Entry - Page 14 http://www.bidforpower.com/forum/attachment.php?s=&postid;=326670 -http://www.bidforpower.com/forum/attachment.php?s=&postid;=327440 - -- SSJ-5-Dan's Entry - Page 15 http://www.bidforpower.com/forum/attachment.php?s=&postid;=327420 - -- Sakana - Page 15 http://members.aol.com/rikoblaze/images/sakana.jpg - -- Aenea - Page 15 http://www.bidforpower.com/forum/attachment.php?s=&postid;=327592 - -- UF's Friend Entry 1 - Page 17 http://www.bidforpower.com/forum/attachment.php?s=&postid;=328948 - -- UF's Friend Entry 2 - Page 17 http://www.bidforpower.com/forum/attachment.php?s=&postid;=328972 - -- Genesite Entry 1 - Page 18 http://www.bidforpower.com/forum/attachment.php?s=&postid;=329352 - -- Chinese Madman - Page 19 http://www.bidforpower.com/forum/attachment.php?s=&postid;=329793 - -- Demian - Page 20 http://www.bidforpower.com/forum/attachment.php?s=&postid;=330408 - -- Kuku247's Entry - Page 20 http://www.bidforpower.com/forum/attachment.php?s=&postid;=330494 - -- Reinako - Page 21 http://home.cfl.rr.com/kashi/reinako.jpg - -- Darkido - Page 22 http://www.bidforpower.com/forum/attachment.php?s=&postid;=330874 - -- Nomas - Page 22 http://www.bidforpower.com/forum/attachment.php?s=&postid;=331009 - -- Lee - Page 23 http://members.aol.com/thecabreras/images/lee.jpg - -- Ree - Page 23 http://members.aol.com/thecabreras/images/ree.jpg - -- Cauli - Page 23 http://www.bidforpower.com/forum/attachment.php?s=&postid;=331908 - -- Wylder - Page 28 http://www.bidforpower.com/forum/attachment.php?s=&postid;=332975 - -Those were the drawings that I felt, after sifting through all 28 pages of the thread, would be the best concept art to base models on. I made this first cut basically by how well thought out the drawing was. I tried to avoid typical drawings, such as characters in ordinary gi's, with ordinary face and ordinary everything else. - -Now, from this list, I narrowed it down even further to a list of 5 males and 5 females. Those are........ - -- Taipan -- UF's Friend Entry 2 -- Nomas -- Wylder -- Adult Chibi - -- Lee -- Ree -- Reinako -- Aenea -- Sakana - -I made these lists by basically picking out the drawings that best conformed to the style of BFP. If this had been an art contest, MutilatedPuppet would have won by far, his pen style artwork is incredible. Had this been a CG contest, Zone would have won by far with his awesome solid-color shading technique. Unfortunately though, neither of these fit the style of BFP, and several others like it also fell through at this point. Great drawings none the less. - -Now, on to the grading. - -Originality - Does this drawing borrow bits and pieces from dbz characters, or other anime characters, or is it unique? - -- Taipan - 9 (Boots like trunks) -- UF's Friend Entry 2 - 10 -- Nomas - 10 -- Wylder - 10 -- Adult Chibi - 8 (Piccolo's entire cape look) - -- Lee - 10 -- Ree - 10 -- Reinako - 10 -- Aenea - 8 (saiyan pads and boots) -- Sakana - 10 - -Not too much of a problem here, all pretty much original drawings for the most part. - -Design - Is the general design of the character executed well or is it plain? - -- Taipan - 10 -- UF's Friend Entry 2 - 10 -- Nomas - 9 -- Wylder - 9 -- Adult Chibi - 8 - -- Lee - 9 -- Ree - 10 -- Reinako - 9 -- Aenea - 8 -- Sakana - 10 - -Style - Does the character fit into the BFP/DBZ universe, or generally speaking, does it look good performing ki attacks and flying. - -- Taipan - 10 -- UF's Friend Entry 2 - 10 -- Nomas - 10 -- Wylder - 10 -- Adult Chibi - 10 - -- Lee - 7 -- Ree - 7 -- Reinako - 7 -- Aenea - 9 -- Sakana - 10 - -Effort - Is it evident that the person spent a lot of time on this drawing, or does it appear rushed. - -- Taipan - 10 -- UF's Friend Entry 2 - 10 -- Nomas - 9 -- Wylder - 10 -- Adult Chibi - 9 - -- Lee - 9 -- Ree - 10 -- Reinako - 10 -- Aenea - 9 -- Sakana - 9 - -EXTRA CREDIT - Not necessary, but rather a point extra added to the score for each thing i notice that grabs my eye about the drawing. - -- Taipan - Excellent work on the CG, +1 -- UF's Friend Entry 2 - -- Nomas -- Wylder -- Adult Chibi - Nice CG, +1 - -- Lee - -- Ree - -- Reinako - +1 for excellent CG work -- Aenea - Beautiful style, color +2 -- Sakana - Nice job with extra attack drawings, plus CG +2 - -FINAL SCORE - -- Taipan - 40 -- UF's Friend Entry 2 - 40 -- Nomas - 38 -- Wylder - 39 -- Adult Chibi - 36 - -- Lee - 35 -- Ree - 37 -- Reinako - 37 -- Aenea - 36 -- Sakana - 41 - -Now, since there is a tie in the male section, what i've decided to do is model BOTH, since each are extremely strong character designs and it'd be a waste to choose one over the other. That makes our winners Taipan, Unnamed(UF's friends entry 2), and Sakana. I will contact the winners tomorrow (a little late to do it tonight, dont you think, heh) and we'll work together to make the character like they envisioned it. Again, thanks to all who entered, it was a great contest and just because you didn't get picked is no reason to feel bad about your art, the only drawback of contests is there can only be so many winners. G'nite. - -nameless - -hotanimebabesdotcom_02 - -(old lost image: https://web.archive.org/web/20011218203246im_/http://members.aol.com/rikoblaze/images/sakana.jpg) - - --Pyro - - - - - - - - -# Komasurao - 09/04/2001 by PyroFragger - -Click Here: https://web.archive.org/web/20020106152249/http://alieninstinct.homestead.com/files/Komasurao.jpg -Its my first serious attempt at skinning an organic model. Hopefully i did ok. It doesn't have a face yet, yes i know, and the skin still needs touchups but atleast you can see what the character itself will look like. Give me suggestions. Komasurao is a combination of "ko"(great) and "masurao"(warrior). Im thinking of putting a japanese symbol on the back of his cape. - -BTW, im gonna send it to rodney so he can render it out like the others and make a character sheet for it, this is just a temporary render. - - - - - - - -# New Character Delay - 09/03/2001 by PyroFragger - - -K, i do have them done, but they are still being worked on and I refuse to rush them. Tomorrow I'll have atleast one finished, SKINNED model rendered in the style as the others. Drawings look good btw, keep it up. Remember, you have till friday. - - - - - - -# BFP Character Drawing Contest - 09/02/2001 by PyroFragger - -I'm sure most of you have seen this by now but if you haven't, for all you artistically inclined people, there's a drawing contest to determine the look of one new bfp character. Details can be found HERE: https://web.archive.org/web/20011031125532/http://www.thejasper.com/DBZ/CosDBZ.html -Good luck. - -PS: HUMAN/SAIYAN!!! IF YOUR CHARACTER IS NOT HUMAN/SAIYAN IT WILL NOT BE CONSIDERED!! NUFF SAID. - - - - - - - -# Take me, I'm yours. - 06/06/2001 by PyroFragger - -It's been decided, the music video will be a long one involving all characters, transitions between each, and a short intro at the beginning. The reason for these videos is to show off all 5 attacks for each characters, as well as melee action and the latest effects, animated, to some of your favorite tunes, not only because we are bored and need something to stall with while we keep pressing for release:) Look for these in the next week (still editing them to fit the music). -As of now, remisser (the music composer, for those that don't know) is finishing some ingame music and it is coming along really well. He'll have his own journal with the new layout, but for right now, he's sharing mine. Expect more from him soon. - --P - - - - - - - -# I stubbed my toenail and now it smells like monkey... - 05/20/2001 by PyroFragger - -I've been talking with a composer over the past couple of days and he is gonna do some music for bfp. I may be using some music from him to do some of the music videos. You can expect all of the music videos with our new layout, which will be coming soon. Thanks for your patience. -P.S. If you get bored or tired of waiting, go to www.newgrounds.com:) - - - - - - - -# The little engine that can - 05/09/2001 by PyroFragger - -I know its been awhile since you've seen an update with new material, but thats only because we are spending more time trying to get this out to you guys. After a long standstill, we are finally getting moving again and BFP will be out before you know it. All we ask for is a little more patience. As for the music videos, I got 185 emails from fans requesting songs. Obviously i couldnt look at them all so Im going to weed through the topics and find the most popular songs for each character. If you want to get your voice in, I suggest replying in one of those topics in the next 30 mins. Later. - - - - - - -# Look honey he plays hockey - 05/03/2001 by PyroFragger - -I just wanted to say this cause no one else is. A lot of people are probably frustrated that we haven't released yet. Well we are definately more anxious to release than any of you are. I can't go into details, but you have to believe that we are doing everything we can to get this out as soon as we can. Bare with us for a little bit. There are things preventing the release that cannot be publicly announced, but we are ready when it gets cleared up. -Anyways, I may do some music videos or something promotional featuring all the characters, all 5 of their attacks, and a transformation. I was going to do this before, but never got around to it. Any song requests for certain people. Krillin automatically gets "Smack my Bitch Up" by prodigy, so thats not an opetion:) But I need song ideas for the rest. - - - - - - - -# ...Ick, no pooh title for this one... - 04/27/2001 by PyroFragger - -My internet went down yesterday. Im on a cable modem, so when they work on the servers, my internet is running at 14,4 baud modem speed at best. Its awful. Well right after i said they would be up and that i still had time on the forums, the cable stopped working. I ran the test on the modem and the cable light was out. Now homestead wont let me on because they are upgrading the server or something, so until then, they arent up. I did have them done yesterday, I swear:) Anyways, hopefully later today i can get into my homestead account. For your information they are pics of Majin Dabura and Chibi Pan. - - - - - -# ...And most of all whinnie the pooh.. - 04/27/2001 by PyroFragger - -'Bout time. -(old lost image: https://web.archive.org/web/20011218203246/http://pyrofragger.homestead.com/files/p21.jpg) - -p22 - -Enjoy. Still to come: Cell form 1 and cell jr. - -Funny quote of the day: "If a blonde throws a grenade at you, pull the pin and throw it back. If a blonde throws a pin at you, run like hell, she's got the grenade in her mouth!" - - - - - - - -# ...there's rabbit..and piglet....and there's owl..... - 04/23/2001 by PyroFragger - -This has nothing to do with BFP at all (except for the little blurb at the bottom) but I took the time to learn MAX modeling over the past couple of days. All high poly stuff, but the princliple of making the initial mesh is still low poly, so I may transcend from Raydream to MAX, I just hope it improves my quality. Here's the thing i made, anyways. My first mesh completely modeled in MAX:) - - -BFP: -Im gonna go against what I said last time just this once so that you can look forward to the part 2 model selection on Thursday. - -Funny Quote of the Day: "Never call Victoria's Secret and ask if something comes in childrens size" - - - - - - - -# ...and Tigger, and little Roo..... - 04/14/2001 by PyroFragger - -*BIG* update: -First, I just want to say that the gi versions of Krillin and Gohan will not be part of the main download on account of some problems I was having with MAX (totally corrupted the file). They will be released later on as an addon. Im incredibly tired of physiquing and animating so today I took a break from all of that. Here is the result: - -checkthisout - -Hope that tides you over for a while. I also want to say that I am no longer giving out public release dates because things tend to happen that are not in my favor, and I always end up with a lot of disgruntled people on my case. - -You can expect more models sometime soon, but like i said, im not going to say when:) - -As for why I'm working on models that fit in phase 3, it is because most of our phase 2 models are done and I was just pretty bored and tired of animating. Now I have them done, and don't have to worry about them when the time comes. - -Check out all the losers wearing dbz costumes (sorry if you're one of them): https://web.archive.org/web/20011031125532/http://www.thejasper.com/DBZ/CosDBZ.html - -Peace - --P - - - - - - - - -# ...A donkey named Eyore is his friend... - 04/12/2001 by PyroFragger - -Good things and bad things. -Good: I found an easy way around the deformation problems the two models have been having so I can finally get them working right. As of now, they deform like hell, but that will all be fixed. If all goes well, I will have in-game shots of them tomorrow. - -Bad: I have put my website on hiatus so those bitching about it will have to chill. Things come up, I'll finish it when I have nothing at all to work on. In the mean time, you can read other tutorials, but mine's gonna take longer than I thought because I have to give the same steps for two different modeling packages, so it takes twice as long. Check back Saturday for a status report, I *may* be finished the tut by then, but don't hold me to that (and you wonder why we don't give release dates when we don't know if we can keep them:) I have other stuff to show off too on Saturday. Peace. - --P - - - - - - - -# ...For most of his childhood days... - 04/05/2001 by PyroFragger - -Check for the skins in Ansel's journal in the next few minutes. I gotta roll out, so I dont have time to upload a shot. The torso is going to be seperated from the legs because i dont have tags on them yet. Tomorrow I can work the kinks out and maybe have an ingame screen for each of them. --P - - - - - - - -# ..There lies an enchanted neighborhood - 04/03/2001 by PyroFragger - -I will be finished with everything relating to phase 1 by tomorrow, so my site will be finished soon. I had hoped to finish it much sooner but such is life. Check for screenshots either tomorrow or thursday of them skinned. - - - - - - -# ...where Christopher Robin plays... - 03/27/2001 by PyroFragger - -Those expecting the tutorial will have to wait. BFP work comes first, tut comes second. If you didnt already know we are finishing things and I have to of course get my models done really soon. They are already attached to skeletons and are animated, in the process of getting skinned, so they will be done soon. I may have the tut up by this weekend. Ive got more work to show later, not for phase 1. --P - - - - - - - -# Deep in the hundred acre woods..... - 03/16/2001 by PyroFragger - -Applied BFP standard animations and attack animations to the new krillin and gohan, ready for skinning soon. Also, I am designing a website for myself that will include shots of m bfp work and my first ever modeling tutorial that is nearing completion. --P - - - - - - - -# New Krillin and Gohan - 02/24/2001 by PyroFragger - -Two reasons for this: -1) tired of everyone telling me to change their outfits to Gi's (Your post clenched it Mr. Krillin ;) - -2) wasnt happy with the level of quality of the models compared to my newer models - -So......here they are, unskinned: - -Krillin: 790 polygons -Gohan: 892 polygons - -new_krillin_gohan - -FEEDBACK - - - - - - - -# Random Updates - 02/09/2001 by PyroFragger - -Been working on Cell perfect a tad bit more to get it ready for Roney's skin job. Cell form 1 is next. I also may be making a RoQ movie file for the ending of BFTP. Lastly, I've been working on a 32 person tourney/capture the dragonballs map. Until phase 2 is released, itll just be a big tourney map. Screenshots of that as well soon. Thats about it, later. - - - - - - -# One more update from me today - 02/09/2001 by PyroFragger - -As I said, I've been working on cell. This is the result, a more stylized, sleek, and muscular Cell: - -(old lost image: https://web.archive.org/web/20011218203246im_/http://pyrofragger.homestead.com/files/Final_Cell.jpg) - -Feedback. - - - - - - - -# First Update - 12/17/2000 by PyroFragger - -My first dev journal on the new site. As of now, im tweaking Gohan and Krillin while the others finish up. Work continues on Cell as well: - -(old lost image: https://web.archive.org/web/20011218203246im_/http://pyrofragger.homestead.com/files/p_cell.jpg) - -Anyways, i dont have much to do right now for phase 1, so I will probably be starting some giant tourney maps due to the 1.27 patch. - -My first dev journal on the new site. As of now, im tweaking Gohan and Krillin while the others finish up. Work continues on Cell as well: - -(old lost image: https://web.archive.org/web/20011218203246im_/http://pyrofragger.homestead.com/files/p_cell.jpg) - +# Meteor - 12/16/2001 by PyroFragger + +I spent a couple hours today helping dash with his new map by creating this meteor map object that rests in the crater. + +coolmeteor + +It looks 10x as cool ingame. + + + + + + +# Hehe, i bet you thought i forgot:) - 12/13/2001 by PyroFragger + + +Trying to be a little more faithful to my promises, spent the last 4 hours working on this: + +TetRender + +Excuse the slow load times of the bfp server. This is Tetsedah, one of the bfp character drawing contest winners. I sat down and reconstructed the mesh using my basemodel instead of Rodney's. Here's a picture of the sketch that Jeff aka Dark Cloud drew: + +contest3 + +Also, that awkward shadow on the collar of the jacket is just a rendering artifact, no idea what causes this but it's not part of the model so don't worry. + + + + + +# Arggg - 12/12/2001 by PyroFragger + + +My computer crashed and i lost some tweaking work i had done to the model. Not a problem, but i have to postpone the shots till tomorrow. Hopefully this won't happen once i have more RAM. Home stretch:) I aim to have both of the models ingame with temporary solid color skins by the end of this weekend, hopefully the skins will be done really soon. +/me hands Ansel more gatorade + +Go Go Go! + + + + + + +# Go baby Go - 12/12/2001 by PyroFragger + + +I can have Ryuujin INGAME the day after i get the skin from Ansel. In the mean time, i work on the second model. It\'s close to done, i\'ll show it off later tonight around 10 oclock pm EST. The finish line is in sight, cheer ansel on! +/me hands Ansel a gatorade + + + + + + + +# Ryuujin Model Finished - 12/04/2001 by PyroFragger + + +Finished all the stuff i need to do for Ryuujin and last weekend sent the maps to Ansel for skinning. Here's a pic: + +RyuujinRender + +It was gonna be in the monday update but there was no monday update so i'm posting it here. + + + + + + + +# Information - 11/22/2001 by PyroFragger + + +This isn't an eyecandy update, just an informational one. As yrgol said, we can release BFP around a week after he has all the models completed. I have ryuujin completed and ready to go ingame as soon as i get a skin from ansel (who's on vacation at the moment) and will have my second character completed sometime next week after i get another concept from ansel. By that time Pyrate will most likely be ingame, so we should be seeing a release very shortly. In my next update i'll have snazzy posed renders of Ryuujin and Komasurao, not saying when though because there's too much that can happen that can delay a date. + + + + + + +# Ryuujin Coming Soon - 11/17/2001 by PyroFragger + + +Sometime this week i'll have pictures of Ryuujin for you guys. The model is done and uv mapped, but i have to redo much of the physiquing and ansel still has to skin it. You'll see skinned renders in here soon as well as ingame shots on the mainpage. + + + + + + +# BaseModel Version 2.0 - 11/13/2001 by PyroFragger + + +For those who care, i reuploaded the BaseModel zip with fixed weighting on the ass. It was collapsing during some of the animations, fixed that right up. Version 2.0, same link as below. +Also, the biped is HIDDEN, not missing. Just go to the fifth tab and unhide all. + + + + + + + +# The Basemodel - 11/12/2001 by PyroFragger + +Here it is. It's fully UV Mapped and Physiqued , with all the tags correctly parented to the biped. For those of you without MAX, there is also the 3ds version that is simply uv mapped, you'll have to attach it to a skeleton in whatever program you use and parent the tags aswell. The uv mapping is done in such a way that all you need is ONE texture for the entire upper and lower body. The head has its own mapping (the head mapping is very very messy but it gets the job done, fix it if you feel like it). I recommend 1 128x128 texture for the head and a 512x512 for the body. +*!*!* UPDATE *!*!*!*! I've ported it to MAX 4. If you've downloaded it already and use MAX4, redownload now! + +Download It Here (broken link): https://web.archive.org/web/20011218203246/http://www.bidforpower.com/Pyro/BaseModel.zip + +Enjoy. Remember, ALL that i ask is for a little credit, and please, if you're going to edit it, edit it enough that its a new character, don't just change his hair to give him a mohawk and release it. I want to see quality character models made from this. + +EDIT: Also, if (for whatever reason) you've seen any bfp skins at all, you'll notice that the uv mapping is set up in the same way as Rodney's. Again, this is NOT Rodney's basemodel, you can check the uv wireframes as well as the actual geometry yourself if you're skeptical. + + + + + + + +# Homestead blows - 11/10/2001 by PyroFragger + + +Seems every time i use homestead to show pictures to you guys i exceed my monthly bandwidth in only one day, lol. If anyone has another free hosting site that allows img tagging (i.e. NOT GEOCITIES!), tell me. + + + + + + +# BAMMMMMMMMMM! - 11/09/2001 by PyroFragger + + +Heh, if you live in another timezone, sue me, its still 11 oclock over here. I still have some work to do on the model but i figure i'll show you what i have today. This is about 6 and a half hours worth of work: + +(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base1.jpg) + +(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base2.jpg) + +(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base3.jpg) + +(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Base4.jpg) + +This isn't really Komosurao, just threw his head on the basemodel i'll be releasing later on (most likely tomorrow, i spent more time on the model than i thought i would, but it payed off). The more experienced MAX users on the editing forum should find this useful as either a guide or as a base for their own models. Enjoy. + + + + + + + + +# On Track - 11/08/2001 by PyroFragger + + +All's in order for tomorrow, tomorrow i will be releasing the following to the budding designers in our community: +A MAX file containing my new basemodel i am using to create my bfp characters. The model will be uv mapped and physiqued to a standard bfp skeleton + +A copy of the bfp animation list and an explanation for all of them + +I'll also have pics of my actual bfp models. + + + + + + + +# Friday Update - 11/06/2001 by PyroFragger + +Thank God i'm through with all this work, i'm home free for a bit. Look for an update friday with new stuff. + + + + + + +# Info - 11/01/2001 by PyroFragger + +I don't really spend much time here anymore obviously, mostly because schoolwork this year really picked up. As it stands, i have two exams tomorrow, and two large reports due tuesday and wednesday, followed by some more exams. I'd say after next week, i'll be in the clear for a bit, but until then its tough to say how much i'll be able to do. I MAY be able to squeeze a couple hours of work into Saturday. What i've decided to do is make my own base model for all my characters. Until now, i'd been using Rodney's because it had been determined that the new bfp would just be different skins and head models on the same body. Well we've obviously strayed away from that to give the finished product more variety, so i'd like to make my own basemodel instead of simply just edit other people's work. The same head models will stay though, since those are mine. Once my basemodel is done i'll be able to more easily invent new characters off of it, and i will release the finished, uvmapped, and physiqued basemodel to developers in the community in the form of a bfpsdk to base their own custom bfp models on, provided that in the readme you mention the basemodel came from me. +Most of you have probably already read yrgol's dev journal. That's exactly what i'll be doing with all of my custom models in the future. As he said, there's no way it will be in the first release (which is why i'll only be releasing one of my models for the first release), but eventually all models will have visually different attacks and auras. + +Here's some information on the characters that will eventually be in a future release of bfp: + +Komasurao - Decided to make him evil. He has a very dark, cruel energy. He's a very deceptive and ingenious fighter, he can trick other fighters with illusions and his attacks pack a punch. + +Ryuujin - Stands for Dragon God in japanese. He is, obviously, a god who has spent the past 3 millenia training his mind and his body. He rarely speaks, and is rarely seen, but he always shows up when there is trouble. His ki is "different," kind of mysterious and very powerful. He wields two scerpent-dragon swords whose blades are made of the same ki as a kienzan. + +Tetsedha - A very determined and confident, but not overconfident, fighter. He lives for the fight and trains continously. His ki is very cold and his attacks are slower than those of Taipan. He takes fighting very seriously and is easily a match for anyone. + +Taipan - A very aggressive, firey fighter who is easily ticked off and a force to be reckoned with. He is the rival of Tetsedha, and also lives for the fight. The only thing that keeps him going is the continous struggle to become better than him. This competition has made him a very talented and dangerous fighter. His ki is very firey and his attacks are quick. + +Drexx - As a teenager, he was very violent and irritable, so he was taken in by a master to try and turn his anger and rage into controllable energy. After 18 years of training, he has become one of the most elite fighters in the universe. He possesses a very electric ki, and he is scared of no one. + +Obviously i'm not the most reliable person in the world right now, so i can't make a prediction on when these will all be done, but i can they'll be done soon. I'll try and squeeze in time here and there inbetween working sessions. + + + + + + + +# ........ - 10/20/2001 by PyroFragger + + +Well i'm getting over this motivational slump i've been in for the past month. A lot of things been going on where i just either couldn't find the time for BFP, or just didn't feel like working on it. For those that have seen office space, you know what im talking about. There was about a 3 week period where i just didn't care. Anyways, i figure its best to just knock this out and get it over with. + + + + + + +# Stay away from teh light!!!111111 - 10/12/2001 by PyroFragger + + +Lol............... +The long story is: I was drinking my tea and eating my crumpets when gooey purple aliens teleported into my room. I was like, WTF? and they said: "Come with us, we will teach you many alien bondage techniques." I forget how it ends. + +The short story is: I lost my homestead account and was too tired to reg a new account. + + + + + + + +# LOL - 10/11/2001 by PyroFragger + + +Heh, got so busy yesterday completely forgot about yesterday's update. Anyways, later tonight, unless i forget again (j/k, i wont). + + + + + + +# BOOJAH! - 10/08/2001 by PyroFragger + + +K, im going to show you EVERYTHING ive done so far on wednesday. Unfortunately the contest winners and another 2 models i had going will not go into the initial release of BFP, i'm only allowed to have 1 go in as of now, due to the attack system. However, they will become available in a release sometime after the first release, sorry about that, but its out of my hands. So it'll either by the one i've already pimped on the forums or another model i made that you haven't seen yet, but none of the contest entries will go into the first release, not fair to the other winners. Anyways, check back on wednesday, i'll have some cool stuff. + + + + + + +# Yadda Yadda Yadda - 09/22/2001 by PyroFragger + + +No pics yet, sorry about that but if you've been following this project long enough you're probably used to it:) Anyways, other things have been occupying my time lately and i just haven't had much time/energy to sit there and work on the bfp models. We do have a deadline though, although i cant say what that is, and we are working towards it so the light at the end of the tunnel is within reach. Whenever i have pics ill show em here, not gonna say when because i dont like breaking promises and it'll be cooler as a surprise. +-P + + + + + + + +# New Characters, Sneak Peek - 09/14/2001 by PyroFragger + + +These aren't finished, but i'm a little too burned out right now to continue modeling tonight, so for now you can see the bodies of the two male winners of the drawing contest. Tomorrow, i'll have the female winner as well as all three head models on, and sometime this upcoming week i'll have snazzy new renders of these three and 2 others of my own creation:) K, nuff stalling: + +(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Taipan_body.jpg) + +(old lost image: https://web.archive.org/web/20011218203246im_/http://alieninstinct.homestead.com/files/Tetsdeha_body.jpg) + +Enjoy. + + + + + + + +# The Winners!!!! - 09/07/2001 by PyroFragger + +Finally, I have sifted through all the contest winners, and here they are. First of all, let me thank everyone who set time aside for this contest. As an artist myself i know sometimes when you enter contests and don't win, it can be depressing, but I saw a lot of impressive drawings in that thread, a lot of you guys have a lot of talent, so keep at it. Without further adieu, here are the entries that made the first cut: + +- DR - page 1 http://darkbostudio.hypermart.net/gallery/darkrapsmall.jpg + +- Kyuusei - page 3 http://www.bidforpower.com/forum/attachment.php?s=&postid;=319192 + +- Kaku - page 3 http://yosefselim.homestead.com/files/Kaku.jpg + +- MITSUKAI - page 4 http://www.bidforpower.com/forum/attachment.php?s=&postid;=319373 + +- Gotrun - Page 8 http://www.bidforpower.com/forum/attachment.php?s=&postid;=321186 + +- God's Entry - Page 8 http://www.bidforpower.com/forum/attachment.php?s=&postid;=321522 + +- Zone - Page 8 http://zone-archive.com/temporary/zonebfp.jpg + +- Adult Chibi - Page 9 http://www.bidforpower.com/forum/attachment.php?s=&postid;=322672 + +- Asura - Page 10 http://www.bidforpower.com/forum/attachment.php?s=&postid;=323126 + +- Cpider's Entry - Page 11 http://www.bidforpower.com/forum/attachment.php?s=&postid;=323770 + +- SSJ-5-Dan's Entry - Page 11 http://www.bidforpower.com/forum/attachment.php?s=&postid;=323994 + +- Yutaka - Page 11 http://danznation.netfirms.com/scans/yutaka1.jpg + +- SSJ-5-Dan's Entry - Page 12 http://www.bidforpower.com/forum/attachment.php?s=&postid;=324903 + +- Taipan - Page 13 http://www.bidforpower.com/forum/attachment.php?s=&postid;=325435 + +- MutilatedPuppet's Entry - Page 14 http://www.bidforpower.com/forum/attachment.php?s=&postid;=326670 +http://www.bidforpower.com/forum/attachment.php?s=&postid;=327440 + +- SSJ-5-Dan's Entry - Page 15 http://www.bidforpower.com/forum/attachment.php?s=&postid;=327420 + +- Sakana - Page 15 http://members.aol.com/rikoblaze/images/sakana.jpg + +- Aenea - Page 15 http://www.bidforpower.com/forum/attachment.php?s=&postid;=327592 + +- UF's Friend Entry 1 - Page 17 http://www.bidforpower.com/forum/attachment.php?s=&postid;=328948 + +- UF's Friend Entry 2 - Page 17 http://www.bidforpower.com/forum/attachment.php?s=&postid;=328972 + +- Genesite Entry 1 - Page 18 http://www.bidforpower.com/forum/attachment.php?s=&postid;=329352 + +- Chinese Madman - Page 19 http://www.bidforpower.com/forum/attachment.php?s=&postid;=329793 + +- Demian - Page 20 http://www.bidforpower.com/forum/attachment.php?s=&postid;=330408 + +- Kuku247's Entry - Page 20 http://www.bidforpower.com/forum/attachment.php?s=&postid;=330494 + +- Reinako - Page 21 http://home.cfl.rr.com/kashi/reinako.jpg + +- Darkido - Page 22 http://www.bidforpower.com/forum/attachment.php?s=&postid;=330874 + +- Nomas - Page 22 http://www.bidforpower.com/forum/attachment.php?s=&postid;=331009 + +- Lee - Page 23 http://members.aol.com/thecabreras/images/lee.jpg + +- Ree - Page 23 http://members.aol.com/thecabreras/images/ree.jpg + +- Cauli - Page 23 http://www.bidforpower.com/forum/attachment.php?s=&postid;=331908 + +- Wylder - Page 28 http://www.bidforpower.com/forum/attachment.php?s=&postid;=332975 + +Those were the drawings that I felt, after sifting through all 28 pages of the thread, would be the best concept art to base models on. I made this first cut basically by how well thought out the drawing was. I tried to avoid typical drawings, such as characters in ordinary gi's, with ordinary face and ordinary everything else. + +Now, from this list, I narrowed it down even further to a list of 5 males and 5 females. Those are........ + +- Taipan +- UF's Friend Entry 2 +- Nomas +- Wylder +- Adult Chibi + +- Lee +- Ree +- Reinako +- Aenea +- Sakana + +I made these lists by basically picking out the drawings that best conformed to the style of BFP. If this had been an art contest, MutilatedPuppet would have won by far, his pen style artwork is incredible. Had this been a CG contest, Zone would have won by far with his awesome solid-color shading technique. Unfortunately though, neither of these fit the style of BFP, and several others like it also fell through at this point. Great drawings none the less. + +Now, on to the grading. + +Originality - Does this drawing borrow bits and pieces from dbz characters, or other anime characters, or is it unique? + +- Taipan - 9 (Boots like trunks) +- UF's Friend Entry 2 - 10 +- Nomas - 10 +- Wylder - 10 +- Adult Chibi - 8 (Piccolo's entire cape look) + +- Lee - 10 +- Ree - 10 +- Reinako - 10 +- Aenea - 8 (saiyan pads and boots) +- Sakana - 10 + +Not too much of a problem here, all pretty much original drawings for the most part. + +Design - Is the general design of the character executed well or is it plain? + +- Taipan - 10 +- UF's Friend Entry 2 - 10 +- Nomas - 9 +- Wylder - 9 +- Adult Chibi - 8 + +- Lee - 9 +- Ree - 10 +- Reinako - 9 +- Aenea - 8 +- Sakana - 10 + +Style - Does the character fit into the BFP/DBZ universe, or generally speaking, does it look good performing ki attacks and flying. + +- Taipan - 10 +- UF's Friend Entry 2 - 10 +- Nomas - 10 +- Wylder - 10 +- Adult Chibi - 10 + +- Lee - 7 +- Ree - 7 +- Reinako - 7 +- Aenea - 9 +- Sakana - 10 + +Effort - Is it evident that the person spent a lot of time on this drawing, or does it appear rushed. + +- Taipan - 10 +- UF's Friend Entry 2 - 10 +- Nomas - 9 +- Wylder - 10 +- Adult Chibi - 9 + +- Lee - 9 +- Ree - 10 +- Reinako - 10 +- Aenea - 9 +- Sakana - 9 + +EXTRA CREDIT - Not necessary, but rather a point extra added to the score for each thing i notice that grabs my eye about the drawing. + +- Taipan - Excellent work on the CG, +1 +- UF's Friend Entry 2 - +- Nomas +- Wylder +- Adult Chibi - Nice CG, +1 + +- Lee - +- Ree - +- Reinako - +1 for excellent CG work +- Aenea - Beautiful style, color +2 +- Sakana - Nice job with extra attack drawings, plus CG +2 + +FINAL SCORE + +- Taipan - 40 +- UF's Friend Entry 2 - 40 +- Nomas - 38 +- Wylder - 39 +- Adult Chibi - 36 + +- Lee - 35 +- Ree - 37 +- Reinako - 37 +- Aenea - 36 +- Sakana - 41 + +Now, since there is a tie in the male section, what i've decided to do is model BOTH, since each are extremely strong character designs and it'd be a waste to choose one over the other. That makes our winners Taipan, Unnamed(UF's friends entry 2), and Sakana. I will contact the winners tomorrow (a little late to do it tonight, dont you think, heh) and we'll work together to make the character like they envisioned it. Again, thanks to all who entered, it was a great contest and just because you didn't get picked is no reason to feel bad about your art, the only drawback of contests is there can only be so many winners. G'nite. + +nameless + +hotanimebabesdotcom_02 + +(old lost image: https://web.archive.org/web/20011218203246im_/http://members.aol.com/rikoblaze/images/sakana.jpg) + + +-Pyro + + + + + + + + +# Komasurao - 09/04/2001 by PyroFragger + +Komasurao + +Its my first serious attempt at skinning an organic model. Hopefully i did ok. It doesn't have a face yet, yes i know, and the skin still needs touchups but atleast you can see what the character itself will look like. Give me suggestions. Komasurao is a combination of "ko"(great) and "masurao"(warrior). Im thinking of putting a japanese symbol on the back of his cape. + +BTW, im gonna send it to rodney so he can render it out like the others and make a character sheet for it, this is just a temporary render. + + + + + + + +# New Character Delay - 09/03/2001 by PyroFragger + + +K, i do have them done, but they are still being worked on and I refuse to rush them. Tomorrow I'll have atleast one finished, SKINNED model rendered in the style as the others. Drawings look good btw, keep it up. Remember, you have till friday. + + + + + + +# BFP Character Drawing Contest - 09/02/2001 by PyroFragger + +I'm sure most of you have seen this by now but if you haven't, for all you artistically inclined people, there's a drawing contest to determine the look of one new bfp character. Details can be found HERE: https://web.archive.org/web/20011031125532/http://www.thejasper.com/DBZ/CosDBZ.html
+Good luck. + +PS: HUMAN/SAIYAN!!! IF YOUR CHARACTER IS NOT HUMAN/SAIYAN IT WILL NOT BE CONSIDERED!! NUFF SAID. + + + + + + + +# Take me, I'm yours. - 06/06/2001 by PyroFragger + +It's been decided, the music video will be a long one involving all characters, transitions between each, and a short intro at the beginning. The reason for these videos is to show off all 5 attacks for each characters, as well as melee action and the latest effects, animated, to some of your favorite tunes, not only because we are bored and need something to stall with while we keep pressing for release:) Look for these in the next week (still editing them to fit the music). +As of now, remisser (the music composer, for those that don't know) is finishing some ingame music and it is coming along really well. He'll have his own journal with the new layout, but for right now, he's sharing mine. Expect more from him soon. + +-P + + + + + + + +# I stubbed my toenail and now it smells like monkey... - 05/20/2001 by PyroFragger + +I've been talking with a composer over the past couple of days and he is gonna do some music for bfp. I may be using some music from him to do some of the music videos. You can expect all of the music videos with our new layout, which will be coming soon. Thanks for your patience.
+P.S. If you get bored or tired of waiting, go to www.newgrounds.com:) + + + + + + + +# The little engine that can - 05/09/2001 by PyroFragger + +I know its been awhile since you've seen an update with new material, but thats only because we are spending more time trying to get this out to you guys. After a long standstill, we are finally getting moving again and BFP will be out before you know it. All we ask for is a little more patience. As for the music videos, I got 185 emails from fans requesting songs. Obviously i couldnt look at them all so Im going to weed through the topics and find the most popular songs for each character. If you want to get your voice in, I suggest replying in one of those topics in the next 30 mins. Later. + + + + + + +# Look honey he plays hockey - 05/03/2001 by PyroFragger + +I just wanted to say this cause no one else is. A lot of people are probably frustrated that we haven't released yet. Well we are definately more anxious to release than any of you are. I can't go into details, but you have to believe that we are doing everything we can to get this out as soon as we can. Bare with us for a little bit. There are things preventing the release that cannot be publicly announced, but we are ready when it gets cleared up.
+Anyways, I may do some music videos or something promotional featuring all the characters, all 5 of their attacks, and a transformation. I was going to do this before, but never got around to it. Any song requests for certain people. Krillin automatically gets "Smack my Bitch Up" by prodigy, so thats not an opetion:) But I need song ideas for the rest. + + + + + + + +# ...Ick, no pooh title for this one... - 04/27/2001 by PyroFragger + +My internet went down yesterday. Im on a cable modem, so when they work on the servers, my internet is running at 14,4 baud modem speed at best. Its awful. Well right after i said they would be up and that i still had time on the forums, the cable stopped working. I ran the test on the modem and the cable light was out. Now homestead wont let me on because they are upgrading the server or something, so until then, they arent up. I did have them done yesterday, I swear:) Anyways, hopefully later today i can get into my homestead account. For your information they are pics of Majin Dabura and Chibi Pan. + + + + + +# ...And most of all whinnie the pooh.. - 04/27/2001 by PyroFragger + +'Bout time.
+(old lost image: https://web.archive.org/web/20011218203246/http://pyrofragger.homestead.com/files/p21.jpg) + +p22 + +Enjoy. Still to come: Cell form 1 and cell jr. + +Funny quote of the day: "If a blonde throws a grenade at you, pull the pin and throw it back. If a blonde throws a pin at you, run like hell, she's got the grenade in her mouth!" + + + + + + + +# ...there's rabbit..and piglet....and there's owl..... - 04/23/2001 by PyroFragger + +This has nothing to do with BFP at all (except for the little blurb at the bottom) but I took the time to learn MAX modeling over the past couple of days. All high poly stuff, but the princliple of making the initial mesh is still low poly, so I may transcend from Raydream to MAX, I just hope it improves my quality. Here's the thing i made, anyways. My first mesh completely modeled in MAX:) + + +BFP: +Im gonna go against what I said last time just this once so that you can look forward to the part 2 model selection on Thursday. + +Funny Quote of the Day: "Never call Victoria's Secret and ask if something comes in childrens size" + + + + + + + +# ...and Tigger, and little Roo..... - 04/14/2001 by PyroFragger + +*BIG* update: +First, I just want to say that the gi versions of Krillin and Gohan will not be part of the main download on account of some problems I was having with MAX (totally corrupted the file). They will be released later on as an addon. Im incredibly tired of physiquing and animating so today I took a break from all of that. Here is the result: + +checkthisout + +Hope that tides you over for a while. I also want to say that I am no longer giving out public release dates because things tend to happen that are not in my favor, and I always end up with a lot of disgruntled people on my case. + +You can expect more models sometime soon, but like i said, im not going to say when:) + +As for why I'm working on models that fit in phase 3, it is because most of our phase 2 models are done and I was just pretty bored and tired of animating. Now I have them done, and don't have to worry about them when the time comes. + +Check out all the losers wearing dbz costumes (sorry if you're one of them): https://web.archive.org/web/20011031125532/http://www.thejasper.com/DBZ/CosDBZ.html + +Peace + +-P + + + + + + + + +# ...A donkey named Eyore is his friend... - 04/12/2001 by PyroFragger + +Good things and bad things. +Good: I found an easy way around the deformation problems the two models have been having so I can finally get them working right. As of now, they deform like hell, but that will all be fixed. If all goes well, I will have in-game shots of them tomorrow. + +Bad: I have put my website on hiatus so those bitching about it will have to chill. Things come up, I'll finish it when I have nothing at all to work on. In the mean time, you can read other tutorials, but mine's gonna take longer than I thought because I have to give the same steps for two different modeling packages, so it takes twice as long. Check back Saturday for a status report, I *may* be finished the tut by then, but don't hold me to that (and you wonder why we don't give release dates when we don't know if we can keep them:) I have other stuff to show off too on Saturday. Peace. + +-P + + + + + + + +# ...For most of his childhood days... - 04/05/2001 by PyroFragger + +Check for the skins in Ansel's journal in the next few minutes. I gotta roll out, so I dont have time to upload a shot. The torso is going to be seperated from the legs because i dont have tags on them yet. Tomorrow I can work the kinks out and maybe have an ingame screen for each of them. +-P + + + + + + + +# ..There lies an enchanted neighborhood - 04/03/2001 by PyroFragger + +I will be finished with everything relating to phase 1 by tomorrow, so my site will be finished soon. I had hoped to finish it much sooner but such is life. Check for screenshots either tomorrow or thursday of them skinned. + + + + + + +# ...where Christopher Robin plays... - 03/27/2001 by PyroFragger + +Those expecting the tutorial will have to wait. BFP work comes first, tut comes second. If you didnt already know we are finishing things and I have to of course get my models done really soon. They are already attached to skeletons and are animated, in the process of getting skinned, so they will be done soon. I may have the tut up by this weekend. Ive got more work to show later, not for phase 1. +-P + + + + + + + +# Deep in the hundred acre woods..... - 03/16/2001 by PyroFragger + +Applied BFP standard animations and attack animations to the new krillin and gohan, ready for skinning soon. Also, I am designing a website for myself that will include shots of m bfp work and my first ever modeling tutorial that is nearing completion. +-P + + + + + + + +# New Krillin and Gohan - 02/24/2001 by PyroFragger + +Two reasons for this: +1) tired of everyone telling me to change their outfits to Gi's (Your post clenched it Mr. Krillin ;) + +2) wasnt happy with the level of quality of the models compared to my newer models + +So......here they are, unskinned: + +Krillin: 790 polygons +Gohan: 892 polygons + +new_krillin_gohan + +FEEDBACK + + + + + + + +# Random Updates - 02/09/2001 by PyroFragger + +Been working on Cell perfect a tad bit more to get it ready for Roney's skin job. Cell form 1 is next. I also may be making a RoQ movie file for the ending of BFTP. Lastly, I've been working on a 32 person tourney/capture the dragonballs map. Until phase 2 is released, itll just be a big tourney map. Screenshots of that as well soon. Thats about it, later. + + + + + + +# One more update from me today - 02/09/2001 by PyroFragger + +As I said, I've been working on cell. This is the result, a more stylized, sleek, and muscular Cell: + +(old lost image: https://web.archive.org/web/20011218203246im_/http://pyrofragger.homestead.com/files/Final_Cell.jpg) + +Feedback. + + + + + + + +# First Update - 12/17/2000 by PyroFragger + +My first dev journal on the new site. As of now, im tweaking Gohan and Krillin while the others finish up. Work continues on Cell as well: + +(old lost image: https://web.archive.org/web/20011218203246im_/http://pyrofragger.homestead.com/files/p_cell.jpg) + +Anyways, i dont have much to do right now for phase 1, so I will probably be starting some giant tourney maps due to the 1.27 patch. + +My first dev journal on the new site. As of now, im tweaking Gohan and Krillin while the others finish up. Work continues on Cell as well: + +(old lost image: https://web.archive.org/web/20011218203246im_/http://pyrofragger.homestead.com/files/p_cell.jpg) + Anyways, i dont have much to do right now for phase 1, so I will probably be starting some giant tourney maps due to the 1.27 patch. \ No newline at end of file diff --git a/docs/old_dev_journals/remisser_dev_journal.md b/docs/old_dev_journals/remisser_dev_journal.md new file mode 100644 index 0000000..cc975c1 --- /dev/null +++ b/docs/old_dev_journals/remisser_dev_journal.md @@ -0,0 +1,502 @@ +# patience - 01/18/2002 by Remisser + +a lot of people are sending emails and other such things frantically asking about the release. Please have patience. It's coming. Chris never said the time was precise. Patience is a virtue. Don't go downloading the supposed leaks. There's a bug with the leaks, and the latest version was uploaded _last night_, by the amazing YRGOL. I recommend you start sending him complimentary notes of thanks for dealing with the tedious coding. :)
+pyrofragger on the other hand is a beligerent drunkard. :) + + + + +# to tide you over - 01/18/2002 by Remisser + +To keep you busy while you wait the next few hours out:
+[www.fark.com](https://web.archive.org/web/20020210152114/http://www.fark.com/) (fun news)
+[www.seanbaby.com](https://web.archive.org/web/20020123041221/http://seanbaby.com/) (funny stuff)
+[www.incisionstudios.com](https://web.archive.org/web/20011130114126/http://www.incisionstudios.com/) (good music - some from the next release of BFP [after this one coming out]) + + + + +# BFP CHAT - 01/18/2002 by Remisser + +So the game will be released today around 9PM EST (I say EST because so many people keep asking me because they can't convert pacific time) +Exciting? Yes. + +So you may want to chat about it. Go to www.mirc.com and download a chat client called MIRC. Then come on into irc.ENTERTHEGAME.com in the channel #FunTimes * and chat with us. (also, #HHMod is a fun channel. :)) +
+
+
+*not actual channel. + + + + +# d b z - 01/15/2002 by Remisser + +

+DBZ SUCKS!!!
+I'm glad this mod isn't tied to DBZ anymore. It'll be interesting when the side packs come out with the DBZ characters in it, but that cartoon is so tired now.
+
+
+---- +

+My music will unfortunately not be in the release this month due to miscommunication and time restraints. But hopefully with the next RC. It'll give you another reason to come back for more. :) + + + + +# merry - 12/25/2001 by Remisser + +Merry Christmas. I've been away for a bit from everything because of major problems, but I hope to fulfill all the commitments I had. This Sunday I will make all the music I have done for Bid For Power downloadable. Along with that I will have a journal entry that goes in depths explaining how I create music, with pointers, using example from the music that I've made for Bid For Power. People can always learn how to model, code, map and everything else quite easily, but it's always hard to get pointers on music without getting the runaround. I can't promise the music pointers by sunday, but I'll try.
+I also want to thank everyone for their dedication to the game. Do you want to know HOW dedicated some of you people are? [Click here to read a fanfic for Bid For Power/Ranma/Tenchi/Slayers/Project A-Ko/Sailor Moon/Dragon Ball Z](https://web.archive.org/web/20020811050438/http://www.cse.unl.edu/~jwessels/fanfics.html) - It's pretty cool, and is still under development. I found it while searching for sites about BFP. It uses the new characters (like Gothax and such) + +Also for your enjoyment:
+[A seasons greeting from Pancake Bunny.](https://web.archive.org/web/20011203120347/http://www.selfabort.com/) + +[My crappy life, as it unfolds.](https://web.archive.org/web/20011116022908/http://www.livejournal.com/users/missing/) + + + + +# update shmupdate - 11/29/2001 by Remisser + +So far I've gotten some good leads from coders (I still have more to email for a followup) and all is going well.
+Sometime this week (when I find that darn password) I will try to fix anything wrong with the PHP of the site. If any of you notice anything wrong with the php, email me so I can try to fix it. + +Lastly, I've been getting forwarded emails. One such email (which was sent to me by a few different people) was about how AOL and Microsoft were teaming up to beta test some new email software and if you forwarded it to one person you got $202 dollars, and more for each other person you sent it to. + +Now, I hate having to be the partypooper, but, the email is fake. First of all there is no way in the entire universe that they would be able to track all the mail sent around the internet from the numerous millions of people in the populace. Secondly, if they were even to ADMIT to being able to doing that, they would get sued for invasion of privacy. Thirdly, if they were to pay off every shmuck and loser who actually BELIEVED that it was possible, it would amount in the high TRILLIONS of dollars. So, yeah, right... AOL/Microsoft have this high spy secret software that can track any mail I send through an Apple IIe computer. Did I also mention they put a microchip in my brain and visit me at night through my aluminum foil thinking cap? It's true. I have to listen to Barney and Friends Super Duper CD just to stop their transmissions from coming in at night.
+Just because the devil forged an unholy alliance between Microsoft and AOL doesn't mean they have this ludicrous tracking power. You ever read those emails about how 'if you forward this, 3 cents will be donated to the cancer society of america' type emails? Guess what. They can't track it. Ever get 'sign this petition and forward it' emails? Guess what, those petitions, even IF they could track it and ORGANIZE it, it would be rejected because a computer could, just the same, scan a phonebook and come up with a random list of names for the petition. + +Chainmails are dumb. Don't send me any. I will not scroll to the bottom and make a wish. It won't come true unless I work for it. I will not be cursed if I don't forward an email. I just broke 15 mirrors, had 20 black cats cross my path, and I walk under ladders on a regular basis. If I'm going to die this gruesome death, I'll do it on my own terms. Think you've got this really funny email? Keep it to yourself, if I want humor I'll visit seanbaby.com, or fatchicksinpartyhats.com - I don't need your 'a priest and a rabbi' jokes that make your Moms and Dads laugh. + +I hate forwarded emails. + +-Trake Adams ( www.incisionstudios.com - I've got some BFP music on the radio station there :)) + +PS - Forward this journal entry. ;) + + + + +# code - 11/27/2001 by Remisser + +Anyone who can code well for Quake 3, send along a message to me. I need as much input as I can about quake 3 music related things. I'm trying to make a .pk file and... well, without the specifics, just send me an email if you're a good quake 3 coder.
+remisser@selfabort.com
+remisser@postmark.net + + + + +# darn dirty journal - 11/07/2001 by Remisser + +

+If you are good with FLASH sites and/or animations, read the end of this.
+---- +

+ +I had this wonderfully long journal entry I made. Opera made it disappear. Oh well. The basics of what I said was 'Problem solved with devenum.dll - I actually had to boot up in safe mode, then shell to DOS and delete the devenum.dll and replace it with the devenum.dll file I downloaded weeks ago. Thanks for all your responses, but none of you came up with that suggestion. :) You see, I had to shell to DOS because if I didn't, the /windows/system/ registry automatically replaces the devenum.dll file with an exact replica of the file, no matter if you DELETE IT, RENAME IT, OR EVEN TRY TO EDIT THE RAW CODE. Booting up in safe mode allows it so that 99% of your DLL's never get used, so therefore the registry has no reason to automatically replace them. Thank you for being patient with me, to-nite I'll be installing Windows 2000 Professional to get rid of some of my woes. I also plan on finishing up the projects I've been working on (BFP, GundamUniverse, Heroes in a Halfshell, et cetera.) Music for Bid For Power is done, on my part, and I'm pretty sure GPoodle is almost done with his music. As I stated with a problem early, the music will probably just be an addon pack unless I can find some way of making the music files smaller. I got a lot of people suggested converting to .mp3 format. Now, really... I'm a musician. If I knew a way of making a .pk file for quake 3 arena that allowed the playing of MP3's, I would use it, any person knows mp3 files are much smaller than wav files. And .ogg files as well. But I believe you can only .pk wave files. Correct me if I'm wrong. Well that's about it. Thanks for your patience and keep up the good work in helping us out.'
+^^^ That's basically what I said... + +Now as I began to mention at the top, I need a Flash person. I run www.selfabort.com - a site that currently will only be in regards to my music. I could use someone's help in designing a flash website (something dark and ominous) and in return I can either make some small bits of music for any projects you may have, or the sound effects, or I can give you a pag redirect. like www.selfabort.com/yournamehere/ -- Or you could just help me out of the kindness of your heart. If you have a friend that does flash, get HIM/HER to email me. :) They may not even be interested and I'd hate to go asking out of the blue. If you can include some urls or samples to your work,it'd be appreciated. I'm desperate. :) + +Thanks again,
+Remisser + + + + +# HELLLLLLLLLP!!! - 10/30/2001 by Remisser + +I need help!
+No matter what audio program I try to use I get the following error everytime: + +[Program name I'm using] has caused an error in
+DEVENUM.DLL
+[Program name] will now close. + +Email me at remisser@postmark.net + + + + +# [music] - 10/26/2001 by Remisser + +I compiled the music and it seems it takes up a lot of space... if anyone has any clue as to how to make these files smaller, tell me... .wav is large... + + + + +# [video] - 10/26/2001 by Remisser + +I'm going to see of a possibility of whether or not we can get a music video made with the new characters.
+Seeing as how things are moving along at a rapid pace though, the game would probably be done by the time we'd finish a video. + + + + +# [last entry] - 09/05/2001 by Remisser + +To clarify on my last joural entry: +1. I like GPoodles new song. +2. The sound quality of the mp3 download below this journal entry is a bit compromised.... + +Now for some news. For those concerned with the size of our ingame music will add to the actual BFP download size, we'll probably release the music as a seperate download. I'd rather it be combined, but for the sake of those with 2400 baud modems, I think it's for the better. + +remisser@postmark.net + + + + +# music / voices - 09/04/2001 by Remisser + +I gave Gpoodle a new music program, I hope he's using it. Apparently, he wants his music in the game too... while this is ultimately up to Yrgol and Chris, I might as well try and help him out and get better, right? Hey GP, if you're reading this, I have some plugins for fruityloops for yah! :)
+Moving along, I should have some music uploaded either tonight or tomorrow... the new characters inspired me. + +If you couldn't read Number 17's new journal, it basically said all his sound fx will be used, except for the DBZ voices (like the angry Gohan scream)... I'm going to see about actually implimenting some anime type screams and yells for the game, with my vast array of microphones and vocal effects modules. + +That's about it. Go to www.hhmod.com for my ninja turtles TC, if you're bored. :) We still need mappaers and such. + +REMISSER@SELFABORT.COM + + + + +# DOWNLOAD! W00t! - 09/04/2001 by Remisser + +remisser@postmark.net
+A download should be available (courtesy of Bandit- from [blink.net](https://web.archive.org/web/20020119214720/http://www.blinkk.net/) who graciously let me upload it) of a song in progress... the mp3 encoding is screwed up, so you'll hear little glorps and scratches... email me at remisser@postmark.net - maybe you can tell me some japanese phrases i should use to make a demo of new character voices for the team to hear. :) ANYHOW, [CLICK HERE TO DOWNLOAD](https://web.archive.org/web/20020210152114/http://www.blinkk.net/~remisser/sweetbfp.mp3) - I incorporated some of the stuff you people suggested (faster beats, more of a punch) - I collaborated on this song with a friend and I've added much more that will be uploaded later. + +I still need MAPPERS for my mod. :) [www.hhmod.com](https://web.archive.org/web/20020210152114/http://www.hhmod.com/) :) + + + + +# [c0ntr0v3rZy] - 08/17/2001 by Remisser + +My modem didn't fare well last night. So, I'm updating from work. No music will be available until I get my shiny new cable modem. This should be soon. +Moving on,
+Some people [who we will call 'minorities'] don't like my updates. They think they're useless. Other people [we'll call 'the majority'] like the fact that someone posts often. I do only what the MAJORITY cares for, not the MINORITIES. If you dislike my updates, ignore my journal. It's going to be the same drivel, basically.
+If you don't like the humor I display, or if you think I'm on some high-horse, then don't read this journal. Only strange people seem to get my humor, and strange is good. I'm not trying to appeal to a broad audience. I'm just trying to give what little info I have and give a smile. + +As for my public beratement of 'MikeGohan', it's dropped the negative mail I get drastically. It's my perrogative. Someone sent a link to a timeline and said I follow it, I don't. I've always been like this. And I am a rockstar, so excuse my rockstar attitude. I like DBZ, I hardly watch it (time constraints... and lots of La Blue Girl tapes I haven't seen yet) - but I know that this mod will be just as good without the trademarked characters. + +People who come up with timelines stating how standard other people are and how they follow the timeline they made are following another timeline: + +Join an interesting and popular community >> Make a website to add to that community >> Draw visitors by making fun of that very community you secretly admire >> Always be opposed to how things are run for the sake of having a differing opinion and getting the visitors who are sick of the site you bash + +I don't follow timelines. + +Sorry if this update seems ambiguous. + +I'll try to get the music up ASAP. + + + + +# Song - 08/16/2001 by Remisser + +Song download available tonight...
+crappy modem pending. :) + +
+
+
+ +Try to cut down on the IMing of me. :) #HHMod on irc.enterthegame.com is a better alternative than email and AIM, since I respond there. + + + + +# w00t w00t - 08/15/2001 by Remisser + +Well, I have a deadline of 'by this Friday' to have the music for BFP done.
+So... + +Don't know what that means... + +The music video I promised before could not be completed for the obvious trademark reasons... BUT... look forward to one soon with the new models (hopefully.) + +Here's a conversation I had online with that HTML hax0r:
+[http://www.selfabort.com/idiocy.htm](https://web.archive.org/web/20011204130005/http://www.selfabort.com/idiocy.htm) + +And, lastly, thank you for the unwarrented 'mod app' submissions... I do need help for the new one, but wasn't expecting people to pick that up from my last post. :) + + + + +# Mods / Posting / Updates - oh my - 08/14/2001 by Remisser + +Since people seem to like it when an update happens, I offer you this update:
+More common questions I get asked -
+Question: The links to your songs don't work. Why?
+Answer: Well, bfpta's site has moved, among other things... [http://www.bfpta.net/remsongs/](https://web.archive.org/web/20020416021046/http://www.bfpta.net/remsongs/) Go there, click on a song. + +Question: What other things are you working on?
+Answer: Numerous mods... Mainly sound effects for most of the side mods... Most of the mods are so secretive, I can't even tell you what they're about! The mod I'm running is taking up a lot of my time as I'm still looking for coders, mappers, skinners, et cetera... making it impossible for me to work on music at the moment. I am almost finished with the BFP music anyhow... Look for more downloads when my cable modem comes. + +Question: How long does it take you to make a song?
+Answer: It varies. Shortest time was one hour. Longest was 2 months.
+Question: I listened to your songs... they sound staticy and horrible in winamp... why's that?
+Answer: Winamp isn't that great... depending on the version you have... try using a different MP3 media... and don't listen to it on small $5 speakers made of tinfoil. The sound quality will be compromised... plug your headphones into your line in jack. That may help. + +Question: Who invented the cotton gin?
+Answer: African American slave Eli Whitney invented the cotton gin. + +There's five questions answered. Got a comment on any of them? Email me. remisser@postmark.net -- Can't promise I'll reply, but I will at least read it. + +remi ([also remisser@selfabort.com](mailto:remisser@selfabort.com)) + +=====EDIT!
+It appears many people were quick to point out the fallacy of my calling Eli Whitney an African American slave. While this was a joke meant to disillusion some people, I would not have edited this entry had user "Pat" not sent me this alluring photo of Mr. Whitney: +whitney2 + +Mr. Whitney is a SWM from the south who enjoys long leisurely walks along the beach, candle lit dinners, and water sports. Mr. Whitney is dynamite with the ladies and wants you to know how sexy you really are in your corset.
+Mr. Whitney is also in a band called "The Filibusters" and their new album "Rabblerousing" should be released posthaste... once vinyl records are invented. Mr. Whitney is quoted as saying his favorite pickup line is "Why dost thou not place your gluteous maximus upon my lap, so that we shall talk about the first thing that pops up." + + + + +# [The answers] - 08/12/2001 by Remisser + +Questions I've been getting recently:
+Question: When will Bid For Power be released?
+Answer: This is a direct quote from Chris off the main page "Bid For Power is close to being released (about a months time)" + +Question: What will the characters look like?
+Answer: I don't know. I do the music. They're going to release pictures as soon as possible... that's the whole FUN of going to a site... seeing new pictures... Being there when it first comes out... Will they look like DBZ characters? Well, a lot of anime tends to look similar... big eyes, small mouth, funky hair that could impale a normal human being. So, stay tuned. + +Question: What about the moves and the maps?
+Answer: Beats me. Though I think there should be a character that throws squirrels. I don't think Chris would like that... maybe we could change all the characters to Tyler Durden, Narrator, Bob, and Marla from Fight Club. Just turn it into boxing... hey, remember that mod for Doom II where it was called boxing, and they wanted it to be only boxing, but you got to start with the basic gun? Well, maybe that was just me and my friends... + +Anyways... + +I don't know about the moves and maps. I don't ask because it's more fun to wait... but I haven't gotten any requests for new sounds for new moves... so... hmmmm... + +Question: What do you use to make music?
+Answer: Lots of basic instruments (guitar, bass), and lots of computer programs. FruityLoops 3.0, Cool Edit Pro and 2000. Cubase when I'm frisky.
+Some free programs you might want to try: www.brambos.com Tuareg, excellent program for drumloops and random cuts. thrax.box.sk Thrax... an underground community, lists a lot of pay and free programs. Go there. + +Question: I love you remisser! Can I have your children?
+Answer: No. Chances are you're a guy. And you're only 14. That's sick. Stop following me. + + + + +# cornflower blue ties - 08/10/2001 by Remisser + +Just so you know, this is an email I got, and my public response:
+QUOTE:
+..what..you think ill fucking waste my time downloading bfp w/o the dbz charecters??fuck no..if i hear theres a person making a mod for the bfp mod..then maybe..im a big fan of dbz..dont tell us ..hype us on about something you said you were unsure of..AND 1 MORE THING...TAKE DOWN ANY AND ALL DBZ CHARECTER PICS ON YOUR SITE..ITS FALSE ADVERTISING AND WE CAN TAKE MONEY FROM YOU FOR THAT..hey look..a bfp site..ohh cool ..its a mod for q3..oh waite..no..its a fucked up..just maps and sound dbz game..ok..why the pics of the charecters..if there are non?..hmm.. "calls lawyer"..dont fuck with a dbz maniac..just get us the charecters..or have some one secertetly make them..then release a link to the download..not the offiacial download..well..you know..if im disapionted..i have your e-mail addy..and whats that..its a site e-mail addy??..then it will be nice to hack into that html..knowwhatimsayin + +^^^QUOTE ENDS... + +My Response:
+Really. REALLY now? I get these types of emails on a daily basis. On an hourly basis, even. What are you trying to do? Instill the fear of God into me? You know what I'm afraid of? That you'll breed. That people like you actually end up having children. But let's take a heartwarming look at your email. + +"it will be nice to hack into that html" - that is the most dumb thing I've heard all day. Hack into my html? What are you going to do, add a few A HREF and IMG SRC tags? How lame can you get? Maybe if you said 'hack into your servers', I might get scared, but I'm not. Y'see, you only know my postmark.net email address... I doubt you'd be able to 'hack' their 'html'. I have another email address, too. remisser@selfabort.com - but guess what, directnic.com owns that, and if you can 'hack' their 'html', you get a cookie! But, judging by your email address "Mikegohan07@aol.com", the only 'hacking' you know about are scripts you download. + +You don't even make them. + +That's what makes me sick. If you want to be known as a hacker, learn how to code, and say "here's a custom hack I made called Brute Force." If you want to put on a tinfoil hat and complain about the aliens, that doesn't make you a hacker. Sticking feathers up your butt does not make you a duck. + +Not only that you go on to try to force me, the one who makes the music, to take down 'any dbz likenesses', or, as you so elloquently put it: "TAKE DOWN ANY AND ALL DBZ CHARECTER PICS ON YOUR SITE..ITS FALSE ADVERTISING AND WE CAN TAKE MONEY FROM YOU FOR THAT.." -- AOL has a spell check. Have you seen this amazing utility? Maybe you can use it to better 'hack' into the 'html' of your email messages. But, as I was saying... False advertising? Take money from us? Tell me, how are you entitled to money when we are a free entity? What goods and services have you purchased from us? Nothing. You have about as much chance of getting money from me as the bum that holds open the doors of the 7-11 and asks for 'spare change on your way out'... and he probably has better breath than you! As for removing dbz likenesses? + +(old lost image: https://web.archive.org/web/20020210152114im_/http://venus.spaceports.com/~kakarot/images/sexykrillin.jpg) + +(old lost image: https://web.archive.org/web/20020210152114im_/http://venus.spaceports.com/~kakarot/images/sexychichi.gif) + + +How's that? We won't use DBZ characters... we'll use real people that look slightly like them. How's that? Huh?! HOW'S THAT? + +So, go ahead and call your lawyer on your bright red Tonka Toys Playhouse Happy Fun Fone that your mommie gave you, because you're wasting my time. + +[think before you speak] + +.remisser (missingfear on aim)
+Special thanks to Spaceports (http://venus.spaceports.com/~kakarot/special.htm) for my unlicensed use of their unlicensed use of licensed Dragonball Z characters. Hope you don't ge sued! + + + + +# oh, one more thing... - 08/09/2001 by Remisser + +an addition to what I said below, I'll try to get a new song for you to listen to within a few days... I'll be changing the name of the song "Destruction Of Namek" to "Obliteration Of Anime World unrelated to DBZ" + + + + +# What this all means - 08/09/2001 by Remisser + +This is the third day in a row I've written something, so, I'll try to make it somewhat daily... which will eventually fall through since I'll never have something to say every day... +So. A new revelation you are all in a quandy/uproar about. + +Bid For Power without the DBZ related/copywritten/prostituted characters. + +Well, it seemed bound to happen for a while now, and, while most of you are preaching doom/gloom, it's not as bad as you think. + +First off you've been given a release date of sorts - about a month from now. Maybe it'll come sooner. Who knows. + +Some people are as disappointed as I was when I found out they were lipsyncing, and look at the similarities: there are a lot of tongues wagging in BFP land, but not a lot of sound coming out. You're all getting worried over nothing. The members will be removing the dbz likenesses, but keeping the anime feel. I know what you're thinking... you're thinking "What if we decide to come out with an addon pack for BFP that had DBZ characters?" -- Hey, what you do when we release the game is in your hands... We're going to be legally clean... This just means I won't be starting that La Blue Girl mod after all. + +So, in closing, calm down, lighten up... it's not the end of the world. + +remisser@postmark.net + + + + +# Quick Note - 08/08/2001 by Remisser + +I'll make this as short and sweet as possible. +In response to some comments made about my last update: +First off, most of the leaks were .exe files. A sort of setup. .exe's aren't pure files. I don't care if you have a virus scanner endorsed by Billy Bob Thorton, or TV's "ALF", some virus' or trojans can get past your rinky-dink Norton. +Secondly, .pk files are as well UNPURE. A file must be pure to not be infected. DOC files can be infected with trojans just via macros alone. Don't dispute this with me, I really don't care what you think. No amount of self assurance will allow you to hide away from something that can and may destroy your computer. +Third, anyone know a good graphic artist? I can't come up with a logo for my site for crap. Blech. + +Thanks for all responses that were sent to remisser@postmark.net -- even if they were negative. + + + + +# Rebels, all of you... - 08/07/2001 by Remisser + +I'm guilty. I admit it. I've dealt warez and, quite frankly, I'm ashamed and apalled at myself. But those days are over now. I pay for my software. I am now an upstanding citizen of the United States of America. +Why am I saying all of this? The point, you ask? +The point is that once the Cease and Desist order (which, by the way, if it was a legal cease and desist order it would have to be mailed, not emailed, and drafted by a lawyer...) was issued to us, everyone scrambled, people leaked, tongues wagged, and 12 year olds across the globe decided to swamp Infogrames message boards with some ugly feeling of justice pumping through their prebubescent veins. Now, I said I USED to trade warez, I learned the err of my ways. Now, if Funimation claims it's illegal for US to distribute Bid For Power, what makes it right for those who did leak it, to do so? Rule of thumb says never download something not from the official site. If you have BFP on your computers, I say, delete it. Most of them are bound with Back Orafice, some of them have bastardized versions of Sub7 (still functional with the new settings) bound with the exe, and some have trojans and virus' with it. You may have, on your hard drive, the best DBZ game to date, but you may also have something that would allow your Gym Teacher to log remotely into your computer and access ANYTHING on your hard drive. Yes, even those hentai porn pics of Goku and Bulma doing the nasty. So delete it. Use your better judgement. Wait for Chris to peacefully resolve the matter. Have faith. Even if you don't think there's a virus on your computer and you somehow got BFP, delete it. Say to yourself "I don't have the final copy with Remisser's music." -- Say it deep in your throat and make it shout out at the top of your lungs "I'm mad as hell and I'm not going to download anymore!" +For those of you with patience, who didn't download a leak, congratulations. Greg Brady loves you. + +Now a note to funimation. Can't you buy see the potential for funimation sales of funimation merchandise products? I mean, with subliminal advertising alone, you could make an extra fortune. You'll have stacks of unmarked bills you an sit on. Think about it. Think of how well (not at all) the other dbz video games did. Now start to think "Gee... 6 million plus hits to this bfp site... that's about 5,999,998 more than an OFFICAL BFP video game site would get. Hmmm... is there any way we can capitalize on millions of page views?" + +And another thing I want you kind folks at Infogrames and Funi to know. Think this game will damage profits? Hardly. Anyone who wants BFP has to have Quake 3 Arena knowledge. They also have to purches Q3A before they can play the game. Also they have to have a computer capable of playing it. Let me tell you, anyone who was ever going to get q3a has already purchased it. We're not going to increase their revenue. And, not everyone wants to download a file as large as BFP. So, here are a few ideas and thoughts... 1) Your profits won't decrease, in fact, they might just get better because the forums here are a breeding ground for talk about the new video game you guys are making and how excited everyone is... it's adding to your sales. 2) I hate to say this, but, the game IS far superior to all the other dbz games made, and just gives a benchmark for your team of 'video game designers' to surpass. 3) Would you really sue us? Think of the costs (court, lawyers, otherwise) -- That'll cost about as much as you'll make from a below average DBZ game. At best you'll break even. And most of us are poor and on welfare. So if you sue us, you won't get money, and we'll get book deals and movie-of-the-week deals, and anytime there's a television show about video games, we'll get paid to tell how you molested us with your lawyers. + +Really, all we want is a middleground. Somewhere to meet and discuss the pros and cons. + +We're on your side. The fans are too. + +If you're a fan, take the time out to write to Funimation. Tell them you'll preorder the game. Tell them how good they look in a suit. Kind words go a long way. Stop downloading the virus filled warez bfp leak, stop flooding message boards with negativity, and send a good word or two. + +Oh... and I'm almost finished with the music for BFP. Downloads to come, weather pending. + +email: remisser@postmark.net + + + + +# invisible monster - 06/26/2001 by Remisser + +Within the next few weeks a music video should be available. And, you know, when I promise something it comes true. +The video will be about 2 minutes and 30 seconds in length. I have a song I've done for it and might make it available before the music video. If possible, I'll try to make the music video available with the next couple of days. This is unlikely, but is a possibility. + +Another thing, if you have a Bid For Power Fan or Clan site, I'm always curious and love to see new BFP related sites. For no particular reason... so email me at remisser@postmark.net with your site URL. + +Don't all these devjournal updates just get you giddy? + + + +# Your birth is a mistake you'll spend your whole life trying to correct. - 06/21/2001 by Remisser + +[Download the End Credits](https://web.archive.org/web/20020210152114/http://insite.i700.net/mp3/Remisser_-_End_Credits.mp3)
+Okay, so it's not that big of a download, but it's something to tide you over. Thanks again to bfpta for hosting this stuff. Thanks to people like Bid For Power Nation who are accepting applications for members who are essential to the fabric of the BFP reality, by updating faithfully, and, without thanks, mirroring files. It's people like axsdeny who made an Android 16 wallpaper that bring a tear to my eye, because it's all without thanks. But thank you, the fans. + +As far as everything else is concerned, I am throughly working on more and more level music. There will be about 5-7 songs (more than likely 7), so as to not add a crap load of megs to a future bfp download. So, don't complain. I hope to finish this all up soon, but keep voting (see prev. entry for the poll), and keep emailing. remisser@postmark.net + + + + +# bitch tits - 06/21/2001 by Remisser + + + + + + + + + + +
+
+ BFP Music Poll +
+
+
+ + + +
+ Which version do you like better? +





+ + + + + + + + + + + +
Destruction of Namek (fast)
Destruction Of Namek (normal)
+
+
+ +
+ + Results + +
+
+
+ +The above is the new poll for the two songs (which you can download and are linked below) for you to vote. Much more organized. + +Later tonight I will try to have another song up for download, and I will put up more questions and answers. Any questions regarding the music aspect of Bid For Power, please email them to remisser@postmark.net + + + + +# Nothing of me is original. I am the combined effort of everybody I've ever known. - 06/18/2001 by Remisser + +A few questions have arisen since my induction into the ranks of team member. Here are the most popular: + +_Who are you?_ + +I'm the new Bid For Power music composer/whipping boy. + +_Really? Cool! Can you put an MP3 player in the game?_ + +No. Good God, no. Go to winamp.com and get an mp3 player and use it in the background. I'm going through the hoops to bring you music, don't ask me how to mute it. + +_Put Slipknot music in the game! Put Rob Zombie music in the game! Please?_ + +OK. All the music for the ingame part of BFP will be original as to not incur the wrath of record companies, and, as well, not to be a proverbial legal landmine. Plus, nobody likes Slipknot. (Oh great, now I'm going to get 80 slipknot fans giving crap) + +Okay, I'm not trying to be vicious here, but these are the questions I've been getting. I love all of the input I've been receiving, but I've gotten enough for now... but I still need your help. You want to hear music being done for BFP at the moment? + +[The First Version of Destruction Of Namek](https://web.archive.org/web/20020210152114/http://insite.i700.net/mp3/Remisser_-_Destruction_Of_Namek_V1.mp3)
+This is the slower more anthemic version of a song I'm working on... + +[The Second Version of Destruction Of Namek (clip)](https://web.archive.org/web/20020210152114/http://insite.i700.net/mp3/Remisser_-_Destruction_Of_Namek_(fast).mp3)
+This is basically a clip that culminates previous fan suggestions... one of which was 'speed it up'. So it's +50 BPM faster than version one. + +DOWNLOADS were made available by the folks over at [BFPTA](https://web.archive.org/web/20020210152114/http://www.bfpta.cjb.net/). Bfpta is a website that in the very near future will showcase tips, tricks, tactics, and techniques designed to assist any and all Bid for power players with one goal: Victory. It will house a numerous amount of tips developed by themselves, and sent in by others, From Novice, to Nightmare. The general public can also comment on each tip, to show others what they think, and how it would/did benefit them. -- One of the many BFP sites that are growing on the web... I might feature more in the future, but if I mention them, it doesn't mean BFP endorses them in any manner. :) + +The next time I update, I should have viewer response on which version they liked best, and what direction I'll be going in. As well as what equipment I use, et cetera. (The only comments I want are which version you like better, I don't really need input on how to make music :)) + +Happy downloading. + + + + +# when did the future change from being a promise, to a threat? - 06/15/2001 by Remisser + +This being my first post in my journal, I'd like you all to
+know who I am. I am the new Bid For Power composer. I will be
+delivering some nice crafted songs for the ingame atmosphere. I will
+give you all links to clips of some of the songs in the next few days.
+I know I can't please all of you, but I'll try to please some of you. + +Any suggestions such as songs you think would be good to cover for
+the ingame part of BFP, please either suggest them to me in an email,
+or address the ideas in the forums... thank you, and have a nice day. diff --git a/docs/rodney_dev_journal.md b/docs/old_dev_journals/rodney_dev_journal.md similarity index 97% rename from docs/rodney_dev_journal.md rename to docs/old_dev_journals/rodney_dev_journal.md index c97bf18..e492608 100644 --- a/docs/rodney_dev_journal.md +++ b/docs/old_dev_journals/rodney_dev_journal.md @@ -1,99 +1,99 @@ -# Q3 models get ready to download - 11/13/2001 by Rodney - -my personal website is done, I haven't uploaded it yet, cuz Im trying to find someone to host it for a good deal, if you know of a good deals with unlimited bandwidth, or no charge for bandwidth that would be great. im shooting to get a host by this saturday, so expect the site up in about 2-3 weeks from now. The BFP models will not available yet, cuz of the new way the code is been implemented. So we have to wait til yrgol gives us the go =). -My site is gonna be at www.RodneyOlmos.com so you can try checking everynow and then and youll see junk poping in and out =). - - - - - - - -# Let me rephrase this - 10/05/2001 by Rodney - -Im also looking at the possibilities of making all the characters I made capable of including multiple animations built on the models so if other people want to use them for their mods or anything else related they will also be able to add them to their mod or game using the characters I built, with my permission or requests to use them, and you may not alter them and claim them yours. - - - - - - -# Sound Help - 10/01/2001 by Rodney - -Thanx to all the people that emailed me and offered their help on sound. Im going to be emailing a list of things that "need to be done", the peeps that emailed me, so we can get this ball rolling and get all the characters over with. -We have enough people helping with sounds now=), so thats good. and I think that might be enough - - - - - - - - -# Characters for Quake arena done - 09/26/2001 by Rodney - -Hey all, I update like once every 6 months =) -things that are new are this. -- I just finished paking all the characters for quake3 arena, this includes all the phase 2 characters, the extra dbz characters like yamcha, the chibis, ussj trunsk,etc, and the new bfp characters. In gonna be releasing them fairly soon or as soon as I get all the sounds for each one of the characters, I would really like a hand on sounds, If you would like to help on that please email me at nameck@networld.com the quake3 characters will get released on sets of 5 characters at the time. - -- New BFP characters are also getting paked for release, all the original DBZ phase 1 characters are pretty much done, and I will be releasing them a lil after BFP is released, it will be on paks of 6 characters at the time so you can add them to the new BFP. - -- The other dbz characters will also get released soon after the first pak. like tien yamcha the chibis ussj trunks, gohan and stuff. but when they get released they will be using some one else's attack. example, chibi goten will use goku's attack and such, yamcha will may be use krillins. - -Reasons for this is game balancing. so far there are 6 attack systems that yrgol coded and by adding new attacks for other characters might unbalance the game. hopefully after release we will get feedback from everybody on how the game plays so we can tweak it for balancing and may be adding more set of attacks for other characters, besides the dbz phase 2 needs to be tackled different since we are not gonna use dbz stuff any more. - - -Im also looking at the possibilities of making all the characters I made capable of including multiple animations built on the models so if other people want to use them for their mods or anything else related they will also be able to add them to their mod or game using the characters I built with my permission or request to use them.. - -for now thats it, Ill try to keep updating often til release. - -lates all - --Rodney -www.RodneyOlmos.com - - - - - - - -# .. Finallly Updating =) - 04/03/2001 by Rodney - -Ok.. since phase 1 is officially done, and there isn't much else to do on it, Im gonna update my journal, and proceed to do so this whole rest of the week,Im gonna post all the work I've ever done for BFP here. That includes the BFP logo, 2d menu art, characters and new upcoming characters and so on. -Here is a list of things of work that I've done and some images that should get posted during the week, - -- 16 -- Goku (3 dif versions of it) -- New Vegeta -- USSJ Trunks -- SSJ Gohan -- Piccolo -- Tien -- Freeza -- Yamcha? -- New Characters? -- Chibi Boys, Trunks and Goten -- Standard BFP Animations -- Logo -- 2d Menu art (buttons and such) -- Anthony did the cartoon renders -- Hud -- Avi Renders, Roll Animations - -I think thats it.... - -so I'll post all my work this week. - -c ya - - - - - - - - Test - 12/04/2000 by Rodney - -alright, my first dev update on this new webpage=) +# Q3 models get ready to download - 11/13/2001 by Rodney + +my personal website is done, I haven't uploaded it yet, cuz Im trying to find someone to host it for a good deal, if you know of a good deals with unlimited bandwidth, or no charge for bandwidth that would be great. im shooting to get a host by this saturday, so expect the site up in about 2-3 weeks from now. The BFP models will not available yet, cuz of the new way the code is been implemented. So we have to wait til yrgol gives us the go =). +My site is gonna be at www.RodneyOlmos.com so you can try checking everynow and then and youll see junk poping in and out =). + + + + + + + +# Let me rephrase this - 10/05/2001 by Rodney + +Im also looking at the possibilities of making all the characters I made capable of including multiple animations built on the models so if other people want to use them for their mods or anything else related they will also be able to add them to their mod or game using the characters I built, with my permission or requests to use them, and you may not alter them and claim them yours. + + + + + + +# Sound Help - 10/01/2001 by Rodney + +Thanx to all the people that emailed me and offered their help on sound. Im going to be emailing a list of things that "need to be done", the peeps that emailed me, so we can get this ball rolling and get all the characters over with. +We have enough people helping with sounds now=), so thats good. and I think that might be enough + + + + + + + + +# Characters for Quake arena done - 09/26/2001 by Rodney + +Hey all, I update like once every 6 months =) +things that are new are this. +- I just finished paking all the characters for quake3 arena, this includes all the phase 2 characters, the extra dbz characters like yamcha, the chibis, ussj trunsk,etc, and the new bfp characters. In gonna be releasing them fairly soon or as soon as I get all the sounds for each one of the characters, I would really like a hand on sounds, If you would like to help on that please email me at nameck@networld.com the quake3 characters will get released on sets of 5 characters at the time. + +- New BFP characters are also getting paked for release, all the original DBZ phase 1 characters are pretty much done, and I will be releasing them a lil after BFP is released, it will be on paks of 6 characters at the time so you can add them to the new BFP. + +- The other dbz characters will also get released soon after the first pak. like tien yamcha the chibis ussj trunks, gohan and stuff. but when they get released they will be using some one else's attack. example, chibi goten will use goku's attack and such, yamcha will may be use krillins. + +Reasons for this is game balancing. so far there are 6 attack systems that yrgol coded and by adding new attacks for other characters might unbalance the game. hopefully after release we will get feedback from everybody on how the game plays so we can tweak it for balancing and may be adding more set of attacks for other characters, besides the dbz phase 2 needs to be tackled different since we are not gonna use dbz stuff any more. + + +Im also looking at the possibilities of making all the characters I made capable of including multiple animations built on the models so if other people want to use them for their mods or anything else related they will also be able to add them to their mod or game using the characters I built with my permission or request to use them.. + +for now thats it, Ill try to keep updating often til release. + +lates all + +-Rodney
+www.RodneyOlmos.com + + + + + + + +# .. Finallly Updating =) - 04/03/2001 by Rodney + +Ok.. since phase 1 is officially done, and there isn't much else to do on it, Im gonna update my journal, and proceed to do so this whole rest of the week,Im gonna post all the work I've ever done for BFP here. That includes the BFP logo, 2d menu art, characters and new upcoming characters and so on. +Here is a list of things of work that I've done and some images that should get posted during the week, + +- 16 +- Goku (3 dif versions of it) +- New Vegeta +- USSJ Trunks +- SSJ Gohan +- Piccolo +- Tien +- Freeza +- Yamcha? +- New Characters? +- Chibi Boys, Trunks and Goten +- Standard BFP Animations +- Logo +- 2d Menu art (buttons and such) +- Anthony did the cartoon renders +- Hud +- Avi Renders, Roll Animations + +I think thats it.... + +so I'll post all my work this week. + +c ya + + + + + + + +# Test - 12/04/2000 by Rodney + +alright, my first dev update on this new webpage=) Ok lets see, characters are practically ready for phase1, I got new characters done, but they will not be out til phase 2 comes out The nice thing is once going through the whole learning process of phase1, phase 2 is gonna go by fast, I got 3 new versions of Goku with 3 dif outfits I wont show anything til the characters are done. Oh yea.. I talked to Chris, Im gonna get some renders done of each character so you can use as wallpapers, and also some character roll avis. And Ill just do that every time i get a character completely done. thats it for now. \ No newline at end of file diff --git a/docs/yrgol_dev_journal.md b/docs/old_dev_journals/yrgol_dev_journal.md similarity index 97% rename from docs/yrgol_dev_journal.md rename to docs/old_dev_journals/yrgol_dev_journal.md index 9878dbb..d3fda93 100644 --- a/docs/yrgol_dev_journal.md +++ b/docs/old_dev_journals/yrgol_dev_journal.md @@ -1,919 +1,950 @@ - -# BFP 1.1 Update Information - 02/04/2002 by Yrgol - -The grand list of my updates to the latest build of Bid For Power. Enjoy. - -BFP 1.1 Changes - -features: - -* added a bfp_server.cfg file, which lets server ops control: - -- flight cost, boost cost, ki regen, and ki charge regen. - -* aura color is now forced to red or blue in team games depending on team. - -* new console. - -* tweaked attack shaders for lighting values. - -* new fingerblast look and sound. - -* lighter versions of the maps gptourney1 and gpctf1. - -* added cone of fire support to the attack scripting. - -balance: - -* lowered boost cost significantly - -* upped ki regen rate so that flying at max pl costs nothing. - -* fixed the extra knockback on attacks that use that feature. tornado blast uses this. - -* rebalance of most attacks. - -bugs: - -* fixed the 1v1 server crash. 1v1 servers should be as stable as the other game types now. - -* fixed handling of plugin player models on clients that don't have the model. - -* fixed a "weapon number out of range" message followed by a crash when playing with bots. - -* fixed the appearance of explosion spawn attacks (homing special multi-beam bug). - -* made suicide take away a kill. - -* fixed losses being assigned to the wrong player in 1v1 games. - -* ctf servers show up in 1.0 as lms servers. fixed. - -* survival servers show up in 1.0 as single player servers. fixed. - -* gametype in 1.0 gets cut off in the servers list. fixed. - -* fixed missing friend shader for team games. - -* added dummy player sounds for tetsedah, ryuujin, and pyrate to get rid of some warning messages. - -* fixed the small own aura setting. - -* switched the missile dlights of the two razor disks. - -* fixed tornado blast catching attacks that have higher priority. - -* fixed ki use toggle. - -* fixed position of sparks in power struggles. - - - - -# more stuff for the patch - 01/23/2002 by Yrgol - -* fixed the 1v1 server crash. 1v1 servers should be as stable as the other game types now. -* lowered boost cost significantly -* upped ki regen rate so that flying at max pl costs nothing. -* fixed a "weapon number out of range" message followed by a crash when playing with bots. -* fixed the extra knockback on attacks that use that feature. tornado blast uses this. -* fixed losses being assigned to the wrong player in 1v1 games. - - - -# bugs so far - 01/20/2002 by Yrgol - -I'm waiting for a decent amount of bugs to show themselves before doing a patch. - -fixed: -* added a bfp_server.cfg file, which lets server ops control: flight cost, boost cost, ki regen, and ki charge regen. -* fixed the appearance of explosion spawn attacks (homing special). -* aura color is now forced to red or blue in team games depending on team. -* ctf servers show up in 1.0 as lms servers. fixed. -* survival servers show up in 1.0 as single player servers. fixed. -* gametype in 1.0 gets cut off in the servers list. fixed. -* fixed missing friend shader for team games. -* added dummy player sounds for tetsedah, ryuujin, and pyrate to get rid of some warning messages. - -known and not fixed yet: -* there seems to be some stability issues with 1v1 servers. this mainly seems to happen on map changes. - - - - -# patch - 01/19/2002 by Yrgol - -The next thing released by bfp will be a dbz plugin media pack or two. This will be an optional download that includes the old models in plugin form and some new maps. - -After that, the next releae of bfp will be a patch that will arrive in about two weeks to a month or so from now. This patch will have a decently small filesize, and it's for fixing whatever bugs show up. If you encounter a bug, please visit the bugs forum to post a message so I can fix it. Once most of the bugs are gone I'll start working on new attacks with some real variety now that my hands aren't tied by dbz. - -The homing special has a graphical glitch. Once it spawns the multiple homing missiles, those missiles appear to each player as his own 4th tier attack instead of appearing as the attack that it is supposed to. This has no effect on how the attack actually operates and does damage, but it can get confusing. This is already fixed for the next version. - - - - -# Modding BFP - 12/13/2001 by Yrgol - -It is possible to modify bfp in a number of ways. Some of them are compatible with the base bfp, and others are destructive to the base bfp. The only type of bfp modding that I will support is the compatible type. -Multiple groups have tried to make their own versions of various hacked copies of bfp, so in order to prevent players from having to choose between versions, I'm posting a little guide on how to make mods of bfp coexist with each other. This is not supposed to be a comprehensive guide on how to mod bfp, just some dos and don'ts if you decide to modify it. - -Model and Map Packs:
-If you only want to create some new models that use existing attack sets, or new maps to play on, create model and map pk3s and release them together so the players can install them in their base bfp directory. These models and maps will be selectable in the menu just like any other model or map. BFP allows for a significant amount of variation in plugin models. - -Game Balance:
-If you are unhappy with the balance of the game and run a server, just modify the script file bfp_weapons.cfg to suit your tastes. You can then start up your server and people using the base bfp will be able to play there. I will be adding more ability to do this in the future, but what already exists is pretty significant. - -New Attacks or Attack Sets:
-If you wish to add new attacks or attack sets to the game, you will have to create a full mod of bfp. This is because otherwise, players will accidentally connect to your bfp mod using the base bfp, and their game is likely to crash. More importantly, I don't want players to have to randomly try bfp servers until they can find one that is compatible with the version they are using. - -The server-finding code in the game will find any server running any mod that lives in a directory that starts with "bfp", and display the full name of that mod. The official bfp files will be in the "bfpq3" directory. Unnoficial mods of bfp can go in directories like "bfpmyversion" or "bfpcool". Players will be able to see "bfpcool" or "bfpq3" in the menu, so that they can find a server compatible with the version of bfp they are running. - -Rule #1:
-Never add or remove anything to any file named similarly to bfp0.pk3 in the base bfp directory. This will invalidate sv_pure, and it will prevent me from patching the game! You can use quake 3's pk3 indexing to override files in bfp0.pk3 without touching that file. - -Rule #2:
-Nobody is getting ahold of my source code for quite a long time, so don't bother asking. If you wish to add new code to bfp, you will have to code the entire game from scratch. - -Rule #3:
-Do not include any official bfp model, map, or image files with your mod. Free game or not, enforcable or not, doing so is a violation of copyrights and is also totally unecessary. Make use of quake3's base mod ability. Quake 3 allows you to create a mod that uses files from another mod's directory. If you do not want your players to have to download the official bfp, you will have to create all new maps and models to use for your game. - - - - -# weapon limit - 12/07/2001 by Yrgol - -Quake3 has a limit of 16 different weapons. BFP used to have a limit of 31 total attacks, with 21 of those being used. Thanks to about 2000 lines of code changes, BFP now has a practically unlimited max number of attacks. -I currently have slots for 99 different attacks that can be added without any code, but I could easily double or triple that if bfp ever comes close to filling up all 99 attacks. - - - - -# mp3 support - 11/30/2001 by Yrgol - -I added Tim "Timbo" Angus' mp3 to wav converter into bfp. It can be found at http://tremulous.sourceforge.net/junk/cg_mp3decoder/ - -This will allow us to include more music in the download, since each music file will have a smaller filesize. - -This will not allow you to play your own mp3's. It is not an mp3 player, it is an in-game utility to convert mp3's into a q3-playable format. - - - - -# Client-side scripting - 11/29/2001 by Yrgol - -I've finished the client side weapon scripting, and I've posted a doc in the editing and modifications forum describing how to use the scripting in order to create plugin models for BFP.
-You can find the post here: -http://www.bidforpower.com/forum/showthread.php?s=dc60acd92fe3f101aff5ad112f78d25d&postid;=508160#post508160 - - - - -# 1.30 and bots - 11/19/2001 by Yrgol - -Oddly enough, the 1.30 point release fixed bot flight, which the 1.17 point release broke. The bots have no problem flying up or down anymore. The bots switch weapons now as well, and I added a few other bot tweaks. -Bfp bots are pretty good for deathmatch practice. They're working much better than I expected. I'm sending a big woot out to the 1.30 point release for q3. - - - - -# - 11/17/2001 by Yrgol - -What we are currently working on is equivalent to beta 2 for most mods, or in bfp jargon phase 1. We had phase 1 completed for dbz quite a long time ago, and then the funimation/infogrames stuff happened. Most of the time since then has been used for converting bfp to not use dragonball z copyrights. - -The code is ready to go, the ui images have been changed to be legal, and we have several non-dbz maps finished. I currently have 3 of the 6 necessary default models. When I have all 6 default models, I will create a full build for the team to test. If nothing horrible goes wrong we'll probably release about a week after that. - -I've been working on scripting much of bfp. When this is complete, new characters and attacks will not require any additional code. This will allow team members and the community to create new unique characters for future releases. There will be a second release which will likely include more maps and models, some more in-depth gameplay modes, more attacks, and a completed attack scripting system. I will also likely be replacing the melee system with something different. - -I've added much more powerful custom plugin model support. The old dbz models will be able to perform identical to what they did before, provided they are converted to the new format. I have repackaged the old bfp models and included the necessary script files for them to work. I will be working on making the support for customizations much more significant, but that will be for future releases. - -I added server side weapon scripting. It allows a large amount of control over the game balance by the server operators, and makes my job of balancing much easier. It can not yet be used to create new attacks, but it will eventually. While doing this I fixed up a few serious gameplay complaints. - -Direct hit detection has been made more reliable. This makes the kienzan equivalent and the deathball easier to use. - -Explosion damage is no longer all or nothing. The damage decreases as distance from the explosion increases like in baseq3. - -Attack radius has been separated from explosion radius. The attack radius and explosion radius for each of the attacks have been tweaked. - -The knockback physics for attacks and explosions has been seriously tweaked. Attacks can now be used to rocket jump. - -Boost jumping has been toned down. It was simply too excessive before. - -Another total rebalance of all attacks happened. - - - - -# scripting - 10/26/2001 by Yrgol - -There's no way I can get all of this done before release, but this is the direction I'm going in. - -I'm working on attack scripting for servers only. This will allow new attacks of any rough type that bfp already contains to be added to the game with little or no code. I have a script file set up and partially implemented that has like 30 variables for each of the attacks. This will not allow models to define new attacks, but it will allow team members to work on attacks without me, or mods of bfp. It also lets server ops who hate the game balance to tweak it significantly. - -I have plans to add a server script file to define what the attack sets are, and even how many different attack sets there are. This config file will make adding new attack sets for existing attacks a trivial task. The file will look something like this: - -``` -attackset 1 -attack1 15 -attack2 20 -attack3 21 -attack4 2 -attack5 30 -defaultmodel models/players/goku -``` - -Per-skin config files have been added that allow things like attack names and voices to be defined by the skin. I have plans to move a lot of stuff into these config files, including attack shaders, sounds, and models. The individual models will never be able to define how the attacks operate, only how they are displayed to the user. - -I will eventually create a separate server config file that defines all the ki costs for flight and things along those lines. - -I've been working on the server attack config scripting for about a week, and it's a huge job. I can not finish all of this in a reasonable time frame, but eventually bfp will be close to just being an engine with the game all defined by runtime scripts. Once that point is reached I'll no longer be the bottleneck for a lot of the future work, and I can concentrate on gametypes and random stuff. - -I do not expect much of this to be implemented enough to matter before we get to release though. I'd much rather release asap and then improve. - - - - -# Conversion - 09/10/2001 by Yrgol - -With the exception of the names, the attacks are not changing. Most of the maps are not changing either. -The new thrust of development for me is moving as many things as reasonable into the player models. Custom player models can be made for any of the sets of attacks, and I'm working on a way for skins to define the names of the attacks. I don't want to let each skin pick the attacks to use for balance, memory, and implementation reasons. The voices that get played when using certain attacks have been moved into the player model. - -The bfp leak is bfp's biggest competition right now. The release has to be better than the previous stuff. There are a few annoying bugs to work out, and some of the top tier attacks throw balance way off. - -My job has been sapping most of my time and motivation to program lately. - - - - -# HoverQ3 - 08/17/2001 by Yrgol - -I released a little mod called Hover Q3. It includes some art from Pyrofragger, Kit Carson, and Sole. You can find it at http://www.bidforpower.com/yrgol/hoverq3 - -It's a 3 meg download and is a lightweight but fun mod. Don't expect anything like bfp from this, but it's well worth checking out. - - - - -# destructable objects - 07/08/2001 by Yrgol - -Breakable map entities are in, with multiple stages of breaking. I havn't done anything fancy with particles and destructable objects yet. None of the maps currently use this feature. - - - -# stuff - 07/06/2001 by Yrgol - -custom models are fully supported now. if you create a hulk hogan model and put it in /models/players/vegetahogan/, then the hulk hogan model will show up in the ui as a selectable skin for vegeta, brotha. I'm gonna hit you with a final flash in a steel cage. - -beams are bendy now. beams are not guided missiles, they are a beam of energy that gets longer quickly. when you turn left while firing, the beam bends to the right briefly before straightening out again. - -other than that, some random bugfixes in the animation playing and 1.29 fixes. - - - - -# <-- that way - 06/21/2001 by Yrgol - -* fixed animation jitter caused by jump anims that shouldn't be played. -* made it play the run anim faster during boost run. -* fixed the bug that made the ki trail show up behind objects. -* gave the accurate third person crosshair some smoothing to prevent it jumping around. -* added a toggle to use a stable crosshair instead of the accurate one. -* fixed a bug that made killing yourself give you a PL boost. -* made renzoku cost a ki pctage to prevent link overflow -* fixed a cheat that let people get vegeta's attacks with a piccolo model. -* enabled custom player models. - -I have a real job, and right now it's very demanding. When I have time and energy for bfp, I'm trying to polish as much as I can. - -The custom player models are weakly supported. Just for example, if you wanted to make a gundam and put it in bfp with vegeta's attacks, you'd have to create a model and skin in models/players/vegetagundam. Once there, it acts as a vegeta skin, even though it's actually a skin and a model. If you were playing with someone who didn't have your gundam model, they'd see the default vegeta. - -I'll release a far more descriptive doc on how to make a custom bfp model later on when it matters. BFP uses a lot of animations. - - - - -# random update - 05/29/2001 by Yrgol - -had a somewhat productive week. bfp comes in spurts. - -updates: -* accurate 3rd person crosshair. -* particle dust trail for boost running or flying close to the ground. -* particle bubble effects that float to the surface and gather there. -* particle splash + bubbles for entering water. -* particle bubble trail for boost swimming: -particlebubble - -* particle bubble trail for boost flying close to water. -* particle bubble effects for charging underwater: -underwatercharge - -* fixed the waterdance bug. -* lowered zanzoken cost. -* added antigrav rock particles for charging: -groundcharge - -* the antigrav rocks fall to the ground when you stop charging. -* gave rock particles collision detection and bouncing properties - - - - -# survival mode - 04/08/2001 by Yrgol - -I added (past tense) survival mode to the game. This might morph into budokai mode when mappers and I both get some time. The name comes from the mode found in a lot of fighting games. - -Survival mode is basically 1v1 where if you die once, you go to the end of the line. The winner does not get healed in between matches, and people spawning in get power level based on the average like in FFA mode. Spectator scores are saved and visible. The winner is the first person to reach the frag limit. - -1v1 mode is meant for challenge matches. It only works well with about a 20 frag limit. Survival mode is intended to give a 1v1 option where you don't have to wait around for 20 minutes before playing. I hope it finds a home on some servers, because it's the mode I most want to play. - - - - -# sticky keys and fps dependencies - 04/07/2001 by Yrgol - -Melee, block, ki boost, and ki charge had a tendency to get "stuck" in online play when the wrong packet got lost. I changed these keys so that they communicate with the server in the same way firing a weapon does. They shouldn't get stuck anymore even when there's a lot of packet loss. This is a bug that's been present since build 1. I'm glad to be rid of it. -Flight speed was highly dependent on FPS as of rc2. Ki boost cost was highly dependent on FPS since build 1. Both of these are fixed now also. - - - - -# subject? - 04/02/2001 by Yrgol - -From memory since I havn't been keeping close logs lately. - -* got a new job -* moved from pittsburgh to boston -* added particle rocks and smoke to explosions. -* added a particle trail to the particle sparks. -* added particle bubbles to attacks underwater. -* added some particles to power struggles. -* redid the shaders so they don't get overbright. -* simplified the bfp options menu by making explosion effects have a selecter between 4 different levels of intensity. -* made kakusan fire a variable number of balls depending on the amount of time spent charging it. fires from 2 to 6 homing balls. slowed the charge time, fixed the angle of fire on the homing balls. -* made ki drain a percentage for eyebeam, shyogeki ha, and kiaho. -* made kiaho a constant fire attack. decreased the range massively. decreased the knockback somewhat. made it bounce all missile attacks except for deathball and kienzan. -* eyebeam is NOT underbalanced in the slightest in its current form. -* increased the speed of all beam attacks. -* made the menu look prettier. -* added 2 new maps, a giant sound pack from 17, a new console font from gp, a few random things from dash. -* fixed body transformations. -* tried out bfp at 640x480 with the options all turned up on a p3 450 running a TNT ONE 16 MEG VIDEO CARD and got 20-30 fps on danorthern against 3 bots. -* create an installer exe for bfp. - - - - -# particles - 02/16/2001 by Yrgol - -I added some particle effects to the game. I havn't really figured out what to do with them yet, but they are there. Just as a start I added the generic explosion sparks. As with every other effect, if you don't like it you can turn it off in the menu. - -bunch of renzokus fired at a wall - - - - -# Old style update - 02/08/2001 by Yrgol - -changes since the thing we sent to the news sites: -* lowered renzoku cost to 100 -* increased renzoku missile speed to 8000. -* increased renzoku radius to 120. -* lowered renzoku firing speed to 200ms. -* put in a 1 second delay before you can zanzoken out of stun. -* increased SBC radius to 150. -* increased SBC damage from 35%*mult to 40%*mult. -* increased mouthbeam damage from 20% to 30% (effective 60% vs same PL). -* lowered final flash damage from 40%*mult to 25%*mult. -* increased the angry gohan range from 500 to 1000. -* tweaked the angry gohan aura scaling. -* lowered angry gohan ki cost from 200 to 150. -* fixed a bug that made other people's charging flashes not show up. -* fixed the lagometer position -* made an exception so the death ball can compete with beams in collisions. -* fixed a bug that made third person extend into spectator free mode. -* fixed a horrible bug that made players not cycle in 1v1. -* tweaked some LMS code. -* fixed a bug that made the blocking anim play a bit too long. -* fixed the problem with the angry gohan shader. -* added basic power struggles. -* redid kiaiho. -__ -* took away boost speed bonus while _firing_ a beam. -* lowered kamehameha damage from 40%*mult to 30%*mult. -* upped final flash damage from 25%*mult to 30%*mult. -* made deathball work in power struggles. -* put the angry gohan cost back the way it was. -* lowered angry gohan range to 800 from 1000. -* slowed recharge time to 6 seconds. -__ -* added the new gohan and krillin. -* added some attack charge sounds. -* redid the timing on masenko and gave it a HA sound. -* fixed a bunch of post-death bugs. -* removed the generic flash sounds for kamehameha and masenko. -* added a new sprite for final flash. -__ -* added sprites for the big bang, sokidan, masenko, kamehameha, and renzoku. -* made oozaru picking more random. -* fixed the oozaru extendo penis. -* totally redid flight physics. -* changed up and down orientation while flying to be relative to the player's axis. -* added cg_flytilt for tilting the viewpoint when boost flying. -* fixed a bug with zanzoken going through clip brushes. -* increased the size of the smoke ring around explosions. -* added cg_explosionSmoke to turn off the smoke ring. -* redid jump. -* added wall jumps. -* added a short delay between jumps excluding wall jumps. avoiding people looking like bunnies. -* fixed the "slightly sticky ground" flight bug. -* added ctf, but it's not selectable from the menu. -* added more dramatic transformations. they are 5 seconds long and you are invuln during it. - - - - -# Random updates - 02/04/2001 by Yrgol - -Ansel's been making a lot of sprites to replace some of our attack models. Flight physics was totally redone to include momentum. - -CTF mode was added back in, but I don't know if it's going to be a fully supported mode or not. CTF is kinda goofy when you can fly fast, and we don't have any maps for it. CTF should not be confused with capture the dragonballs, which still isn't in phase 1. - - - - -# Power Struggles - 01/10/2001 by Yrgol - -Power struggles are in phase 1 now. Ki recharge time has been increased to 6 seconds for now, subject to change at any given moment. We had it take about 20-30 seconds a long time ago and it really killed the gameplay. Lots of attacks have gone through some rebalancing. - - - -# Yrgol’s Important Bid For Power Phase 1 README!!! - 01/10/2001 by Yrgol - -(complete with 3 different exclamation points!) -A wise man once said: “Those who do not read this document will suck at BFP.” - -What is Bid For Power? -Bid For Power (BFP) is a quake 3 total conversion. We have taken quake 3 and used it to create a new game. In order to play BFP, you will need the full retail version of Quake 3 Arena by ID Software, but once you have that, our game is a free downloadable add-on. - -In phase 1 of BFP, we place 6 of the fighters from the Freeza saga in an arena in order to fight. Power is gained over time, but the way to get power quickly is to kill other players. Obviously in the show, Krillin would not be able to fight Freeza, but if we followed that rule who would play Krillin? In BFP, the only difference between the characters is the set of attacks available. - -Features: -- 6 different selectable characters, each with 5 attacks. Goku, Piccolo, Vegeta, Freeza, Gohan, and Krillin. -- 21 different ki attacks including controllable attacks, homing attacks, and chargeable attacks (no guns!). -- Toggleable flight. -- offhand melee combat -- Re-"charge"able ki energy for ammo, flight, and speed/power boost. -- Power level determines the strength of attacks, speed of movement, max health, max ki, ki charge rate, and what attacks are available. -- 5 different game modes: FFA, 1v1, Team DM, Last Man Standing Team DM, and Oozaru mode. -- Numerous new player animations. -- Plenty of cool effects: head and full body transformations, selectable aura, ki trail, scalable attacks, big explosions, playable third person mode, first person vis mode. - -Installation (windows): -- Install the full retail version of Quake 3 Arena. The demo version will NOT work with BFP. -- Install the 1.27 point release for Quake 3 Arena, found at http://www.quake3world.com/files/patch127g.html -- Unzip the bfprc1.zip file into your quake 3 arena directory. Make sure to use folder names when extracting the files. -- The directory "bfp" will be created as a subdirectory of the quake 3 directory. -- To start the game, either use the "Mods" tab in quake 3, or start the game with command line args “+set fs_game bfp”. Creating a shortcut to quake 3 with that argument is the recommended way of starting BFP. - -Starting BFP:
-There are two ways to start BFP. The easiest way is to use the mods menu in Quake 3. From the main Quake 3 menu, click "mods". Then find "Dragon Ball Z: Bid For Power" on the list and highlight it. If you have a lot of mods installed, you may need to scroll the list in order to make BFP visible. Once you have BFP highlighted, click on the load button and BFP will start. - -The other way to start BFP is to use the command line arguments `+set fs_game bfp`. An easy way to do this under Windows is to create a shortcut. Highlight the quake3 icon in whatever directory you installed Quake 3 into, and press ctrl-c to copy the link. Press ctrl-v to create a shortcut to quake3. Right click on the new shortcut and go into the properties menu. Under the `shortcut` tab of the properties menu, change the target to `C:Quake 3 Arenaquake3.exe +set fs_game bfp` if you installed Quake 3 into C:Quake 3 Arena. Use this new shortcut to start BFP. - -BFP loads its own config file called 'bfp.cfg'. This allows either way of starting BFP to work without any config file problems. - -What is Phase 1? -Phase 1 is the first release of BFP. It covers important characters and places from the Freeza saga of DBZ. - -What is Phase 2? -Phase 2 will be the second major release of BFP. It will be released when it is done, and will cover important characters and places from the Android/Cell saga. Phase 2 will also likely have updated effects and game play. There will more than likely be a few patches between phase 1 and phase 2. - -How do I play a multi-player game? -This game is designed to be played multiplayer. From the main menu, click on the "multiplayer" button. BFP will scan all of the quake 3 arena servers and display any BFP servers that it finds. Once a BFP server is found, highlight it, click accept and the game will connect to the server. - -Is there a single-player game? -Just barely. This game is intended to be played multi-player. A single player game can be played with the bots, but we recommend playing against real people. - -Viewpoint -Viewpoint is how you see the game while you play it. You have your choice of 3 different viewpoints for playing BFP. To switch between the view, use the BFP Options menu. - -- Third Person: The model is seen from the outside. Unlike the standard quake 3 third person view, in BFP the crosshair is visible, and the angle is changed to create less blocking of the view. Cvars cg_thirdPersonAngle and cg_thirdPersonRange have had their cheat protection removed, and cg_thirdPersonHeight was added for additional customization. - -- First Person Vis: First person where your own model is drawn. Your viewpoint is positioned on the model's eyes, and you can see your own arms and legs while fighting. - -- First Person: The standard first person quake 3 view. - -Power Level - -Power level is how strong you are. Power level determines max health, max ki, movement speed, damage caused by attacks, and which attacks are available to use. The only way to get more power is to kill someone. When you die (in most game modes), your power level is reset to the average of all current power levels. When the game ends or the server restarts, the power level of the players is reset to the default. - -Power Tiers - -Power level is divided into tiers. While most power level effects are based on the power level itself, aura color and attacks available is determined by the tier. - -- Tier 1: < 100,000 PL. Blue aura, only one ki attack available. -- Tier 2: 100,000 - 250,000 PL. Red aura, two ki attacks selectable. -- Tier 3: 250,000 - 500,000 PL. Red aura, three ki attacks selectable. -- Tier 4: 500,000 - 999,000 PL. Red aura, four ki attacks selectable. -- Ultimate Tier: 1 mil PL. Yellow aura, all attacks selectable. - -Transformations - -At 1 million power level, the transformation happens. The aura color turns yellow, and if the character has a transformation it occurs. Saiyajin characters turn into Super Saiyajins. Other than the benefits from increased power level, there is no additional benefit of the transformation. - -Ki Energy - -Ki is your stamina. Ki is used as ammunition for the ki attacks. Additionally, ki can be used to increase power, speed, jumping height, and special attacks by using the boost key. Ki can be charged in order to replenish it at any time by holding down the charge key. Beware, while charging you can not move or attack, and are highly visible to other players. - -Flight - -BFP has a toggle able flight button. The default is "f" but it can be set in the controls menu. To begin flying, simply press f once, and once again to stop flying. While flying, you are able to move to wherever you look. The jump key will make you move up, and the crouch key will make you move down. Your ki drains at a steady rate. At low power levels you will need to limit your flight, while at high power levels you can fly all day. A large speed boost is gained by holding down the boost key. - -Melee Combat - -To engage in melee combat, hold down the melee key and put the crosshairs over your opponent. If you are close range, then you will start beating on him. If you are long range, you will dive at the bad guy for a strike. To add extra knock back and stun your opponent, hold down the boost key while melee fighting. - -Hit Stun - -While in hit stun, the player can not move, attack, block, or charge. 1 second of hit stun is added when the player runs out of ki. 3 seconds of hit stun is added when melee is used in combination with the boost key. Do not overlook this feature. Being able to knock people into hit stun with ki-boosted melee is one of the keys to being good at BFP. -*The melee stun can be avoided by holding melee while you are attacked. - -Zanzoken - -Zanzoken is a short, extremely fast movement. In BFP, zanzoken is a controlled short-range teleportation that is performed by double-tapping right or left movement keys. Zanzoken is useful for dodging attacks, and can be used to break out of hit stun after 1 second. - -Blocking - -Pressing block once grants the player between one and two seconds of blocking. Blocking drains ki quickly, but transfers all damage to ki instead of health. Blocking can also be used to deflect missile attacks. There is a one second delay between when the player stops blocking and when the player can block again. - -Ki Attacks - -Each character has 5 attacks. At the lowest power tier, the player only has one selectable attack. Each time the player advances to a new power tier, he gets a new selectable attack. By default, each of the numeric keys, 1-5, choose a different attack. Only one attack can be selected at a time, but switching between attacks is instant. - -- standard attacks: hold down fire and you fire. -- charge-up attacks: hold down fire to start charging the attack, release fire to fire. -- minimum charge-up attacks: same as charge-up attacks, but with a minimum charge-up time. The ball of ki will appear in the player's hands once he is prepared to fire. -- beam attacks: a type of charge-up attack. Can only fire one beam at a time. After firing, you can aim the attack by moving the mouse, or detonate it by hitting fire again. -- homing attacks: these attacks will home in on the nearest opponent. - -Attack Collisions - -Unlike quake 3 attacks, BFP attacks are more than just a trajectory. Attacks have actual dimensions, and will explode on contact with players. If an attack looks 50’ wide, it will hit any players who come within 25’ of the center of the attack. Mid-air attack collisions also happen. Two attacks that collide will damage each other, and the stronger attack will cause the other to explode. The exception is any beam attack, which will destroy any other attack that it makes contact with. - -Power Struggles - -When two beam attacks collide, a power struggle will happen. Instead of the stronger beam blowing up the weaker beam, the stronger beam will push back the weaker beam until it hits the other player. Ki boost can be used to up the power of your beam if you are losing a power struggle. The Death Ball is an exception among the missile attacks, and can be used in a power struggle. - -Krillin - -- Kikou: standard weak ki blast. Press or hold fire to fire a blast. -- Taiyoken: the solar flare. Hold down fire until charged, then release to blind people around you. -- Sokidan: homing ball. Hold down fire until charged, then release to fire. -- Kakusan: a missile that spawns 4 homing balls when detonated. Hold down fire until charged, and release to fire the missile. Press fire again to detonate the missile into 4 homing balls. -- Kienzan: the destructo disk. Hold down fire until charged, and release to fire. This attack goes through walls and does massive damage. - -Vegeta - -- Kikou: standard weak ki blast. Press or hold fire to fire a blast. -- Bakuhatsuha: larger ki blast. Press or hold fire to fire a blast. -- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. -- Big Bang Attack: a beam that has a massive explosion. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate into a massive big bang explosion. -- Final Flash: a large and powerful beam attack. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. - -Gohan - -- Kikou: standard weak ki blast. Press or hold fire to fire a blast. -- Sokidan: homing ball. Hold down fire until charged, then release to fire. -- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. -- Masenko: beam attack. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. -- Energy Dan (Angry Gohan Attack): Gohan creates a massive and continuous explosion around himself. Press and hold fire. After a few seconds the explosion will appear. Gohan can not move while performing this attack. - -Piccolo - -- Kikou: standard weak ki blast. Press or hold fire to fire a blast. -- Eye Beam: a continuous blast of power from the eyes. Press and hold fire. -- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. -- Chobakuretsumaha: a fast and powerful homing attack. Hold down fire until charged, and release to fire. -- Special Beam Cannon: a fast and narrow but extremely powerful spiraling beam. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. - -Goku - -- Kikou: standard weak ki blast. Press or hold fire to fire a blast. -- Kiaiho: An invisible blast of energy from the hands with a massive knockback. Press or hold fire to attack. -- Sokidan: homing ball. Hold down fire until charged, then release to fire. -- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. -- Kamehameha: beam attack. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. - -Freeza - -- Kikou: standard weak ki blast. Press or hold fire to fire a blast. -- Shyogeki Ha: fast firing, extremely fast moving ki blasts from the fingers. Hold fire to attack. -- Freeza Beam: an extremely fast ki attack fired from the finger. Press or hold fire to fire a blast. -- Tsuibi Kienzan: Freeza’s homing version of the kienzan. Hold down fire until charged, and release to fire. This attack goes through walls. -- Death Ball: a gigantic ball of ki. Hold down fire until charged, and release to fire. This attack is huge in size and does massive damage. - -Game Types: - -- Death Match: Players start at ~50,000 PL. Additional PL is gained for each kill. On death, the PL of the player resets to the average of all other PLs. -- Tourney: Same rules as DM, but with only two players at a time. -- Team DM: Same rules as DM, but with teams. -- Team LMS: Last Man Standing. If you’ve ever played clan arena you should know this mode. Players start at 1 mil PL, and players who die can not respawn until the next round. -- Oozaru: This game works as regular FFA, except one player is chosen to be the ~50’ tall Oozaru. The player who kills the Oozaru gets to be the Oozaru next. The Oozaru has extra power and speed, and is also a giant compared to the other players. - -CVARS: - -If you don't already know what a cvar is you should probably ignore this section. These can be used from the q3 console to access features. Most of these features can be set from within the menu. - -* g_basepl [1-999]: set the starting power level from one thousand to one mil. -* cg_kitrail [0-99]: set the length of the ki trail. 0 turns it off. -* cg_crosshairhealth [0/1]: turn on or off the feature that makes the crosshair turn red when you score a hit. -* cg_lightauras [0/1]: turn on or off the aura dynamic lights. -* cg_lightexplosions [0/1]: turn on or off the explosion dynamic lights. -* cg_drawKIWarning [0/1]: turn on or off the low ki warning. -* cg_chargeupAlert [0/1]: turn on or off the "ready" message when charging attacks. -* cg_stfu [0/1]: disable character voices when firing attacks. -* cg_lowpolysphere [0/1]: force the use of a lower polycount sphere. -* cg_explosionShell [0/1]: turn on or off the explosion shell. -* cg_drawOwnModel [0/1]: toggle first person between traditional and vis modes. - -Help my computer can’t handle BFP! - -With all of the graphics options turned on, BFP takes a much beefier system than standard Quake 3. If your computer sucks, don’t despair. We’re looking out for you. BFP is meant to push a p3 500 with 128 megs of memory and a GeForce video card, but it is designed to be able to run playably on a celeron 300 with 128 megs of memory and a TNT2. However, with a much weaker system than that, you are pushing your luck with BFP. - -Here are some things you can do to try to boost your performance. - -- The first thing to try is turning off dynamic lights in the game options menu. Quake 3 can be slow when it comes to handling dynamic lights, and there are a lot of them in BFP. -- Go into the BFP options menu and use the ki trail slider bar to lower the length of the ki trail. Few systems can handle the ki trail at its max length without framerate loss. With the slider bar all the way to the left, the ki trail will have 0 length. With the slider bar all the way to the right, the ki trail will almost stretch the length of our maps. -- Go under the systems setting menu and lower the resolution to 640x480. Don’t gag, most Quake 3 competition players seem to use this resolution. -- Go into the BFP options menu and set the aura type to “polygonal aura”. This is the least costly aura type. Using the BFP options menu, turn transformation aura off, dynamic aura lights off, dynamic explosion lights off, ki trail off, small own aura on, and ssj perma glow off. With these settings BFP should run about as well as quake 3 does on your computer. -- If you are really having trouble go into the video set-up menu and change the lighting from lightmap to vertex. Everything will look crappy, but the game will run faster. -- If your system is running low on memory, use the BFP options menu to turn Force Default Skins on. This is the BFP equivalent of cg_forceModels in Quake 3. Force Default Skins will disable all custom skins for the models. With this option on, there will only be one skin shared between all Vegetas. - -Who made this game, and why is it free? - -Bid For Power was made by a group of volunteers. We’re all DBZ fans and most of us have watched all or nearly all the Japanese episodes. I can’t speak for all the reasons of the individual team members, but we all thought the idea of a first person DragonBall Z game was cool enough to want to get involved. It’s free because we are not a company, and don’t have the money to pay off people like Id. This game was created by people, for fun, in their spare time. Everyone on the team contributed to the game design and art style. - -- Chris James: project lead, coordinator, original concept, PR. -- Yrgol: programming, game design. -- Rodney: Goku and Piccolo models, animation, additional model tweaking, 2d art, skins. -- Pyrofragger: Gohan, Krillin, and SSJ Goku models, animation, DNamek map, aura and attack models. -- NilreMK: Phase 2 and “secret character” models, animation, attack models, Q3 editing guru. -- Mooky: Vegeta and Freeza models. -- Ansel: lead skin artist, 2d art -- Dethayngel: skins. -- Ebola: skins. -- Anthony: 2d art, char select art. -- Badhead: attack and aura shaders. -- Number17: sounds. -- Yngwei: Snake Way map, skyboxes, performed all music used in BFP. -- GangstaPoodle: GPCity, GPNamek, GPCityDark, and GPCave maps and textures, 2d art. -- Kit Carson: Earth and NamekXL maps and textures. -- $onik: Kami’s Lookout map textures. -- Perfect Chaos: additional map textures. -- Nat: webmaster, additional programming. -- Disco Stu: web design - - - - -# zanzoken - 12/17/2000 by Yrgol - -Zanzoken is in now. I'm doing it as a short range controlled-direction teleport. Double tap right or left in combination with other movement in order to zanzoken. I don't want to get into specifics but yes there are restrictions and costs to prevent this from being used all the time. - - - -# stuff - 12/14/2000 by Yrgol - -As you can tell I'm still making the mod. The bots own now. If all that mattered were code, bfp would be ready to release. Everything that I said was going to be in phase 1 is in there now and then some, and the bugs are minimal. A few of our models still need some tweaking before the whole is releasable. - -Funimation did indeed send us a cease and decist letter within days of selling dbz game rights to Infogrames. However, they have decided to take a closer look at the situation. We did not want to jump the gun on being foxed, because things are still fairly up in the air. We did not want the slashdot post to happen. - -Bid For Power will be released. It will most likely have Dragon Ball Z characters in it when it is released. - -17-3 (not finished): -* made bots aim beams. -* made bots charge attacks randomly above the minimum. -* made bots melee. -* made bots act blind when hit with the taiyoken. -* made bots aware of splash damage on charge-up attacks. -* lowered shyogeki ha damage from 20 to 5. -* lowered shyogeki ha range from 8192 to 1200. -* lowered shyogeki ha spread. -* made homing attacks easier to dodge. -* lowered kiaiho range from 8192 to 1000. -* made kiaiho do a stable 20% damage. -* removed the damage boost for ki boosting. -* got rid of the character change messages because they never worked right. -* shrunk the pre-fire deathball a bit so it doesn't clip freeza's head. -* made blindness not last through death. -* got rid of the melee axehandle smash direction. -* fixed a bug in melee that caused a crash. -* lowered hit-stun time from 3 seconds to 2. -* capped self-splash at 20%. -* added in the separate kienzan attack shader. -* shrunk freeza's kienzan. -* fixed a bug that made the kienzans stay around far longer than they should. -* fixed a bug that made kienzans hit way too fast and too often. -* gave the kienzans a max number of hits, which fixes a memory leak. -* fixed a bug with the animation timers. -* fixed a bug with switching weapons while firing a beam. -* fixed a bug with switching weapons while charging up an attack. -* made charging only prevent intentional movement, not glue you in place. - -17-2: -* made bots be 100% agressive all the time. -* made bots fly a better. -* taught bots how to use charge-up attacks and beams. -* fixed bot aim. -* upped the charge time for the chobakuretsumaha and sokidan. -* scaled down the pre-fire flashes of most attacks. -* fixed the spelling and grammer in the bot files and fixed the error messages. - -17-1: -* redid third person view. -* got around the gimble lock for flight so you can go upside down. -* changed default cg_thirdpersonheight to -50. - - - - -# more sutff than you wanted - 11/29/2000 by Yrgol - -16-3/17: -* fixed the timing of the "ready" message with the kamehameha. -* made a bfp.cfg file so that bfp will work fine by using the mods menu. -* fixed a bug that caused untextured weapon flashes to appear. -* fixed a bug that caused attack voices to sometimes come out at the wrong time. -* added cg_stfu to turn the voices off. -* fixed a bug that made melee mess up charge-up attacks. -* fixed a bug that caused a lot of problems in the char select screen. -* fixed a bug that made melee screw up the weapon time and hit way too fast. -* fixed a bug that made the charge-o-meter misread the sokidan/chobakuretsumaha power. -* fixed another bug with the ready message. -* added cull disable to all the explosion shaders so they don't disappear when you get too close. -* added an explosion shell/shockwave. can be turned off with cg_explosionShell 0. -* added cg_lowpolysphere to toggle between a low and a high polycount sphere model. -* fixed the infamous death-spawn bug. -* fixed the sizing of the accept button in the video setup menu. -* seemingly accidentally fixed the problem of ki trails showing through walls. -* made cg_thirdpersonrange and cg_thirdpersonheight get saved in bfp.cfg. -* gave cg_thirdpersonheight a default of 20. -* increased freezabeam explosion radius. -16-2: -* added g_basePL with a slider in the server startup to define starting PL. -* gave the renzoku an alternating firing offset. -* cut chobakuretsumaha damage in half. -* upped the ki cost of the chobakuretsumaha. -* lowered freezabeam damage. -* made melee hit stun not happen when both people are holding melee. -* made homing missiles select a target based on angle instead of distance. -* refined homing angle selection somewhat. -* fixed the sbc spiral so it disappears less often. -* fixed jump (detection and animation). -* made the kienzan stop disappearing when it hits other missiles. -* added self splash damage back in. -* made blocking deflect missiles. -* added in 17's explosion sounds. -* added in character sounds for sbc, kamehameha, final flash, bb attack, masenko. -* hopefully fixed the ride me big boy bug. - -16-1: -* made the spiral not disappear when two people sbc at the same time -* made ki trails a little more robust -* forced r_railCoreWidth to 15 on startup. -* removed a bug with the ssjhead getting an aura when it shouldn't. -* fixed a bug that caused the char select screen to crash the game. -* resized images so they were powers of 2. -* got rid of "unknown gametype" when connecting to a oozaru or lms server. -* homing attacks don't turn all the way around anymore. -* homing attacks no longer lose their velocity when homing. -* tweaked the various homing missile speeds to account for the new code. -* made taiyoken based on FOV, meaning you have to see it to go blind. -* set the size of menu buttons to 80x80 and made those values -settable through one file instead of 30 different files. -* made the eyebeam visible from all perspectives. -* upped the eyebeam damage. -* made the eyebeam not draw when holding melee. -* fixed a rotation problem on the auras. -* fixed a problem with showing the wrong gametype name when starting a server. - -16: -* decreased melee dive range from 2000 to 700. -* hopefully fixed the problem of melee dive teleporting you into objects. -* new menu. -* new attack icons. -* new hud. -* new ki trail. -* fixed the servers menu to differentiate between bfp gametypes. -* removed the "impressive" for the freezabeam. -* lowered the ki cost of the freezabeam from 1000 to 500. -* made the freezabeam explode when it hits a player instead of going through them. -* gave the freezabeam explosion some damage. -* changed the sound played for the freezabeam. -* fixed a bug in the eyebeam that made the beam not show up. -* changed the name of the sbc to makankosappo. -* removed drowning. -* made the speed boost depend on PL instead of power tier. -* made cg_kitrail be a slider var from 0 to 99 for ki trail length. - -15-8: -* removed heals from spawning on maps. -* increased visual explosion size. -* made charging a lot more effective. -* cut damage in half. -* made melee dive a lot more solid. -* gave the melee hit sound an origin. - -15-7: -* made the chargeup alert display in third person -* moved the chargeup alert to the bottom of the screen. -* plays the pain sounds based on percent of max health. -* upped masenko damage. -* made the hud player counter not include dead people. -* removed gibs for killing someone. you can still gib a corpse. -* made the prepare anim play for kakusan. -* fixed the timing on at least some charge up attack anims. -* added a flash model to firing beams. -* added KamehamehaFlashShader etc for the beam attacks. -this shader needs to be opaque enough to hide the beam origin -* scaled down the pre-fire ball for beam attacks. -* removed the standard flash entirely. - -15-6: -* added a "ready to fire" message for charge-up attacks. -* added cg_chargeupAlert to turn off that message. -* fixed a bug that would let attacks get kienzan properties. -* upped kakusan damage some. -* lowered taiyoken damage to 1%. -* added a visibility check on taiyoken. only checks barriers, not direction. -* increased taiyoken chargeup time. -* shortened blindness time some. -* made the melee_axehandle animation run, but it's not very noticable. -* toned down the angry gohan. -* made the prepare anim play for the angry gohan. -* changed the scoreboard name of the angry gohan to the Angry Gohan Attack. -* fixed "the incredible eternal kienzan" bug. - -15-5: -* first person vis mode works. it works best with models that have tag_eyes (gohan/krillin). -* angry gohan no longer draws while meleeing or charging. -* took out the announcer sounds for player names. -* blindness no longer stays with you through death. -* took out the ring model for explosions on players. -* fixed a scaling problem in the explosion ring that made it go black. -* removed the eject brass option from the game options menu. -* individual score now survives death in LMS mode. -* following in LMS spectator mode no longer fucks the next round. -* got rid of some red in the menu. -* gave explosions and missiles dynamic lights. -* added cg_lightExplosions to turn off dynamic explosion lights. -* removed the chargeup anim for taiyoken. - -15-4: lost all code since 15, had to redo stuff. ---SHADERS-- -* added shaders for kamehameha and masenko. -* added separate beam shaders. -* added separate shaders for explosions. -* added a shader for the freezabeam explosion ---WEAPONS-- -* freeza's second attack no longer ejects brass. -* fixed manual beam detonation. -* missiles should collide with each other a bit better. -* fixed homing attack collisions. -* increased eye beam range. -* removed self-damage. -* gave deathball a min charge up time (it was supposed to have one). -* gave masenko a min charge up time (it was supposed to have one). -* decreased the chargeup speed of sokidan/chobakuretsumaha. -* made all weapons explode when they hit a player. -* put the BIG BANG in the BIG BANG attack -* pumped up the deathball. -* can't continue firing a beam while charging, blocking, -melee fighting, or in hit-stun. -* beam attacks use the sphere as the pre-fire flash. -* beams now trump missiles for mid-air collisions. -* lowered renzoku damage. -* made taiyoken work. -* gave the SBC a spiral trail. -* weapon firing sound plays while firing beams instead of -while charging them. -* made the freeza beam red and gave it an explosion. -* gave explosions a smoke ring. ---OTHER-- -* custom skins now work fine. if you use a custom -vegeta skin that I don't have, instead of showing -up as sarge you will show up as vegeta/default on -my screen. -* somewhat improved bots. -* cg_forceModel removed. -* added cg_forceSkin to force the default skin for each model. -* changed obituary messages. -* beam attacks no longer blind you in first person view. -* switched from the nuke model to the sphere model for explosions. -* tweaked explosions a lot. -* picking up a normal health after a mega health won't set -you back to 100% health anymore. -* no longer plays the melee sound for missiles. -* unfucked spectator mode. -* switching chars while in LMS mode causes a suicide like in -any other game mode. + +# BFP MAKES YOU TALK IN CAPS - Yrgol - 05/12/2002 + +For those that were wondering who these guys are, the about section has been updated to include character descriptions and a little backstory for all the fighters. + + + +# 1.2 100,000 - Yrgol - 05/10/2002 + +Version 1.2 of Bid For Power has now been downloaded over 100,000 times. I'd like to give a big thanks out to everyone who stayed with us. + + + +# 1.2 Tourney - Yrgol - 05/08/2002 + +With the demise of the XSL there's another BFP tournament forming. They are having a 1v1, 2v2, and 3v3 tournament with signups going on now. +Visit their page at https://web.archive.org/web/20020810122128/http://bfp-allstars.us4n6.org/ + + + +# Its True, Its True! - Yrgol - 05/06/2002 + +1.2 is now ready for download. The files section of this page has been updated. I will be adding more links as the night goes on. + + + +# 28 hours until 1.2 - Yrgol - 05/05/2002 + +1.2 will be released tomorrow at around 9pm eastern. I added a few images to the action section. When the big fuzzy focus on planetquake gets updated they'll have another 8 screenshots which combined with the new images on this page will cover all the new attacks. + + + +# BFP 1.1 Update Information - 02/04/2002 by Yrgol + +The grand list of my updates to the latest build of Bid For Power. Enjoy. + +BFP 1.1 Changes + +features: + +* added a bfp_server.cfg file, which lets server ops control: + +- flight cost, boost cost, ki regen, and ki charge regen. + +* aura color is now forced to red or blue in team games depending on team. + +* new console. + +* tweaked attack shaders for lighting values. + +* new fingerblast look and sound. + +* lighter versions of the maps gptourney1 and gpctf1. + +* added cone of fire support to the attack scripting. + +balance: + +* lowered boost cost significantly + +* upped ki regen rate so that flying at max pl costs nothing. + +* fixed the extra knockback on attacks that use that feature. tornado blast uses this. + +* rebalance of most attacks. + +bugs: + +* fixed the 1v1 server crash. 1v1 servers should be as stable as the other game types now. + +* fixed handling of plugin player models on clients that don't have the model. + +* fixed a "weapon number out of range" message followed by a crash when playing with bots. + +* fixed the appearance of explosion spawn attacks (homing special multi-beam bug). + +* made suicide take away a kill. + +* fixed losses being assigned to the wrong player in 1v1 games. + +* ctf servers show up in 1.0 as lms servers. fixed. + +* survival servers show up in 1.0 as single player servers. fixed. + +* gametype in 1.0 gets cut off in the servers list. fixed. + +* fixed missing friend shader for team games. + +* added dummy player sounds for tetsedah, ryuujin, and pyrate to get rid of some warning messages. + +* fixed the small own aura setting. + +* switched the missile dlights of the two razor disks. + +* fixed tornado blast catching attacks that have higher priority. + +* fixed ki use toggle. + +* fixed position of sparks in power struggles. + + + + +# more stuff for the patch - 01/23/2002 by Yrgol + +* fixed the 1v1 server crash. 1v1 servers should be as stable as the other game types now. +* lowered boost cost significantly +* upped ki regen rate so that flying at max pl costs nothing. +* fixed a "weapon number out of range" message followed by a crash when playing with bots. +* fixed the extra knockback on attacks that use that feature. tornado blast uses this. +* fixed losses being assigned to the wrong player in 1v1 games. + + + +# bugs so far - 01/20/2002 by Yrgol + +I'm waiting for a decent amount of bugs to show themselves before doing a patch. + +fixed: +* added a bfp_server.cfg file, which lets server ops control: flight cost, boost cost, ki regen, and ki charge regen. +* fixed the appearance of explosion spawn attacks (homing special). +* aura color is now forced to red or blue in team games depending on team. +* ctf servers show up in 1.0 as lms servers. fixed. +* survival servers show up in 1.0 as single player servers. fixed. +* gametype in 1.0 gets cut off in the servers list. fixed. +* fixed missing friend shader for team games. +* added dummy player sounds for tetsedah, ryuujin, and pyrate to get rid of some warning messages. + +known and not fixed yet: +* there seems to be some stability issues with 1v1 servers. this mainly seems to happen on map changes. + + + + +# patch - 01/19/2002 by Yrgol + +The next thing released by bfp will be a dbz plugin media pack or two. This will be an optional download that includes the old models in plugin form and some new maps. + +After that, the next releae of bfp will be a patch that will arrive in about two weeks to a month or so from now. This patch will have a decently small filesize, and it's for fixing whatever bugs show up. If you encounter a bug, please visit the bugs forum to post a message so I can fix it. Once most of the bugs are gone I'll start working on new attacks with some real variety now that my hands aren't tied by dbz. + +The homing special has a graphical glitch. Once it spawns the multiple homing missiles, those missiles appear to each player as his own 4th tier attack instead of appearing as the attack that it is supposed to. This has no effect on how the attack actually operates and does damage, but it can get confusing. This is already fixed for the next version. + + + + +# Modding BFP - 12/13/2001 by Yrgol + +It is possible to modify bfp in a number of ways. Some of them are compatible with the base bfp, and others are destructive to the base bfp. The only type of bfp modding that I will support is the compatible type. +Multiple groups have tried to make their own versions of various hacked copies of bfp, so in order to prevent players from having to choose between versions, I'm posting a little guide on how to make mods of bfp coexist with each other. This is not supposed to be a comprehensive guide on how to mod bfp, just some dos and don'ts if you decide to modify it. + +Model and Map Packs:
+If you only want to create some new models that use existing attack sets, or new maps to play on, create model and map pk3s and release them together so the players can install them in their base bfp directory. These models and maps will be selectable in the menu just like any other model or map. BFP allows for a significant amount of variation in plugin models. + +Game Balance:
+If you are unhappy with the balance of the game and run a server, just modify the script file bfp_weapons.cfg to suit your tastes. You can then start up your server and people using the base bfp will be able to play there. I will be adding more ability to do this in the future, but what already exists is pretty significant. + +New Attacks or Attack Sets:
+If you wish to add new attacks or attack sets to the game, you will have to create a full mod of bfp. This is because otherwise, players will accidentally connect to your bfp mod using the base bfp, and their game is likely to crash. More importantly, I don't want players to have to randomly try bfp servers until they can find one that is compatible with the version they are using. + +The server-finding code in the game will find any server running any mod that lives in a directory that starts with "bfp", and display the full name of that mod. The official bfp files will be in the "bfpq3" directory. Unnoficial mods of bfp can go in directories like "bfpmyversion" or "bfpcool". Players will be able to see "bfpcool" or "bfpq3" in the menu, so that they can find a server compatible with the version of bfp they are running. + +Rule #1:
+Never add or remove anything to any file named similarly to bfp0.pk3 in the base bfp directory. This will invalidate sv_pure, and it will prevent me from patching the game! You can use quake 3's pk3 indexing to override files in bfp0.pk3 without touching that file. + +Rule #2:
+Nobody is getting ahold of my source code for quite a long time, so don't bother asking. If you wish to add new code to bfp, you will have to code the entire game from scratch. + +Rule #3:
+Do not include any official bfp model, map, or image files with your mod. Free game or not, enforcable or not, doing so is a violation of copyrights and is also totally unecessary. Make use of quake3's base mod ability. Quake 3 allows you to create a mod that uses files from another mod's directory. If you do not want your players to have to download the official bfp, you will have to create all new maps and models to use for your game. + + + + +# weapon limit - 12/07/2001 by Yrgol + +Quake3 has a limit of 16 different weapons. BFP used to have a limit of 31 total attacks, with 21 of those being used. Thanks to about 2000 lines of code changes, BFP now has a practically unlimited max number of attacks. +I currently have slots for 99 different attacks that can be added without any code, but I could easily double or triple that if bfp ever comes close to filling up all 99 attacks. + + + + +# mp3 support - 11/30/2001 by Yrgol + +I added Tim "Timbo" Angus' mp3 to wav converter into bfp. It can be found at http://tremulous.sourceforge.net/junk/cg_mp3decoder/ + +This will allow us to include more music in the download, since each music file will have a smaller filesize. + +This will not allow you to play your own mp3's. It is not an mp3 player, it is an in-game utility to convert mp3's into a q3-playable format. + + + + +# Client-side scripting - 11/29/2001 by Yrgol + +I've finished the client side weapon scripting, and I've posted a doc in the editing and modifications forum describing how to use the scripting in order to create plugin models for BFP.
+You can find the post here: +http://www.bidforpower.com/forum/showthread.php?s=dc60acd92fe3f101aff5ad112f78d25d&postid;=508160#post508160 + + + + +# 1.30 and bots - 11/19/2001 by Yrgol + +Oddly enough, the 1.30 point release fixed bot flight, which the 1.17 point release broke. The bots have no problem flying up or down anymore. The bots switch weapons now as well, and I added a few other bot tweaks. +Bfp bots are pretty good for deathmatch practice. They're working much better than I expected. I'm sending a big woot out to the 1.30 point release for q3. + + + + +# - 11/17/2001 by Yrgol + +What we are currently working on is equivalent to beta 2 for most mods, or in bfp jargon phase 1. We had phase 1 completed for dbz quite a long time ago, and then the funimation/infogrames stuff happened. Most of the time since then has been used for converting bfp to not use dragonball z copyrights. + +The code is ready to go, the ui images have been changed to be legal, and we have several non-dbz maps finished. I currently have 3 of the 6 necessary default models. When I have all 6 default models, I will create a full build for the team to test. If nothing horrible goes wrong we'll probably release about a week after that. + +I've been working on scripting much of bfp. When this is complete, new characters and attacks will not require any additional code. This will allow team members and the community to create new unique characters for future releases. There will be a second release which will likely include more maps and models, some more in-depth gameplay modes, more attacks, and a completed attack scripting system. I will also likely be replacing the melee system with something different. + +I've added much more powerful custom plugin model support. The old dbz models will be able to perform identical to what they did before, provided they are converted to the new format. I have repackaged the old bfp models and included the necessary script files for them to work. I will be working on making the support for customizations much more significant, but that will be for future releases. + +I added server side weapon scripting. It allows a large amount of control over the game balance by the server operators, and makes my job of balancing much easier. It can not yet be used to create new attacks, but it will eventually. While doing this I fixed up a few serious gameplay complaints. + +Direct hit detection has been made more reliable. This makes the kienzan equivalent and the deathball easier to use. + +Explosion damage is no longer all or nothing. The damage decreases as distance from the explosion increases like in baseq3. + +Attack radius has been separated from explosion radius. The attack radius and explosion radius for each of the attacks have been tweaked. + +The knockback physics for attacks and explosions has been seriously tweaked. Attacks can now be used to rocket jump. + +Boost jumping has been toned down. It was simply too excessive before. + +Another total rebalance of all attacks happened. + + + + +# scripting - 10/26/2001 by Yrgol + +There's no way I can get all of this done before release, but this is the direction I'm going in. + +I'm working on attack scripting for servers only. This will allow new attacks of any rough type that bfp already contains to be added to the game with little or no code. I have a script file set up and partially implemented that has like 30 variables for each of the attacks. This will not allow models to define new attacks, but it will allow team members to work on attacks without me, or mods of bfp. It also lets server ops who hate the game balance to tweak it significantly. + +I have plans to add a server script file to define what the attack sets are, and even how many different attack sets there are. This config file will make adding new attack sets for existing attacks a trivial task. The file will look something like this: + +``` +attackset 1 +attack1 15 +attack2 20 +attack3 21 +attack4 2 +attack5 30 +defaultmodel models/players/goku +``` + +Per-skin config files have been added that allow things like attack names and voices to be defined by the skin. I have plans to move a lot of stuff into these config files, including attack shaders, sounds, and models. The individual models will never be able to define how the attacks operate, only how they are displayed to the user. + +I will eventually create a separate server config file that defines all the ki costs for flight and things along those lines. + +I've been working on the server attack config scripting for about a week, and it's a huge job. I can not finish all of this in a reasonable time frame, but eventually bfp will be close to just being an engine with the game all defined by runtime scripts. Once that point is reached I'll no longer be the bottleneck for a lot of the future work, and I can concentrate on gametypes and random stuff. + +I do not expect much of this to be implemented enough to matter before we get to release though. I'd much rather release asap and then improve. + + + + +# Conversion - 09/10/2001 by Yrgol + +With the exception of the names, the attacks are not changing. Most of the maps are not changing either. +The new thrust of development for me is moving as many things as reasonable into the player models. Custom player models can be made for any of the sets of attacks, and I'm working on a way for skins to define the names of the attacks. I don't want to let each skin pick the attacks to use for balance, memory, and implementation reasons. The voices that get played when using certain attacks have been moved into the player model. + +The bfp leak is bfp's biggest competition right now. The release has to be better than the previous stuff. There are a few annoying bugs to work out, and some of the top tier attacks throw balance way off. + +My job has been sapping most of my time and motivation to program lately. + + + + +# HoverQ3 - 08/17/2001 by Yrgol + +I released a little mod called Hover Q3. It includes some art from Pyrofragger, Kit Carson, and Sole. You can find it at http://www.bidforpower.com/yrgol/hoverq3 + +It's a 3 meg download and is a lightweight but fun mod. Don't expect anything like bfp from this, but it's well worth checking out. + + + + +# destructable objects - 07/08/2001 by Yrgol + +Breakable map entities are in, with multiple stages of breaking. I havn't done anything fancy with particles and destructable objects yet. None of the maps currently use this feature. + + + +# stuff - 07/06/2001 by Yrgol + +custom models are fully supported now. if you create a hulk hogan model and put it in /models/players/vegetahogan/, then the hulk hogan model will show up in the ui as a selectable skin for vegeta, brotha. I'm gonna hit you with a final flash in a steel cage. + +beams are bendy now. beams are not guided missiles, they are a beam of energy that gets longer quickly. when you turn left while firing, the beam bends to the right briefly before straightening out again. + +other than that, some random bugfixes in the animation playing and 1.29 fixes. + + + + +# <-- that way - 06/21/2001 by Yrgol + +* fixed animation jitter caused by jump anims that shouldn't be played. +* made it play the run anim faster during boost run. +* fixed the bug that made the ki trail show up behind objects. +* gave the accurate third person crosshair some smoothing to prevent it jumping around. +* added a toggle to use a stable crosshair instead of the accurate one. +* fixed a bug that made killing yourself give you a PL boost. +* made renzoku cost a ki pctage to prevent link overflow +* fixed a cheat that let people get vegeta's attacks with a piccolo model. +* enabled custom player models. + +I have a real job, and right now it's very demanding. When I have time and energy for bfp, I'm trying to polish as much as I can. + +The custom player models are weakly supported. Just for example, if you wanted to make a gundam and put it in bfp with vegeta's attacks, you'd have to create a model and skin in models/players/vegetagundam. Once there, it acts as a vegeta skin, even though it's actually a skin and a model. If you were playing with someone who didn't have your gundam model, they'd see the default vegeta. + +I'll release a far more descriptive doc on how to make a custom bfp model later on when it matters. BFP uses a lot of animations. + + + + +# random update - 05/29/2001 by Yrgol + +had a somewhat productive week. bfp comes in spurts. + +updates: +* accurate 3rd person crosshair. +* particle dust trail for boost running or flying close to the ground. +* particle bubble effects that float to the surface and gather there. +* particle splash + bubbles for entering water. +* particle bubble trail for boost swimming: +particlebubble + +* particle bubble trail for boost flying close to water. +* particle bubble effects for charging underwater: +underwatercharge + +* fixed the waterdance bug. +* lowered zanzoken cost. +* added antigrav rock particles for charging: +groundcharge + +* the antigrav rocks fall to the ground when you stop charging. +* gave rock particles collision detection and bouncing properties + + + + +# survival mode - 04/08/2001 by Yrgol + +I added (past tense) survival mode to the game. This might morph into budokai mode when mappers and I both get some time. The name comes from the mode found in a lot of fighting games. + +Survival mode is basically 1v1 where if you die once, you go to the end of the line. The winner does not get healed in between matches, and people spawning in get power level based on the average like in FFA mode. Spectator scores are saved and visible. The winner is the first person to reach the frag limit. + +1v1 mode is meant for challenge matches. It only works well with about a 20 frag limit. Survival mode is intended to give a 1v1 option where you don't have to wait around for 20 minutes before playing. I hope it finds a home on some servers, because it's the mode I most want to play. + + + + +# sticky keys and fps dependencies - 04/07/2001 by Yrgol + +Melee, block, ki boost, and ki charge had a tendency to get "stuck" in online play when the wrong packet got lost. I changed these keys so that they communicate with the server in the same way firing a weapon does. They shouldn't get stuck anymore even when there's a lot of packet loss. This is a bug that's been present since build 1. I'm glad to be rid of it. +Flight speed was highly dependent on FPS as of rc2. Ki boost cost was highly dependent on FPS since build 1. Both of these are fixed now also. + + + + +# subject? - 04/02/2001 by Yrgol + +From memory since I havn't been keeping close logs lately. + +* got a new job +* moved from pittsburgh to boston +* added particle rocks and smoke to explosions. +* added a particle trail to the particle sparks. +* added particle bubbles to attacks underwater. +* added some particles to power struggles. +* redid the shaders so they don't get overbright. +* simplified the bfp options menu by making explosion effects have a selecter between 4 different levels of intensity. +* made kakusan fire a variable number of balls depending on the amount of time spent charging it. fires from 2 to 6 homing balls. slowed the charge time, fixed the angle of fire on the homing balls. +* made ki drain a percentage for eyebeam, shyogeki ha, and kiaho. +* made kiaho a constant fire attack. decreased the range massively. decreased the knockback somewhat. made it bounce all missile attacks except for deathball and kienzan. +* eyebeam is NOT underbalanced in the slightest in its current form. +* increased the speed of all beam attacks. +* made the menu look prettier. +* added 2 new maps, a giant sound pack from 17, a new console font from gp, a few random things from dash. +* fixed body transformations. +* tried out bfp at 640x480 with the options all turned up on a p3 450 running a TNT ONE 16 MEG VIDEO CARD and got 20-30 fps on danorthern against 3 bots. +* create an installer exe for bfp. + + + + +# particles - 02/16/2001 by Yrgol + +I added some particle effects to the game. I havn't really figured out what to do with them yet, but they are there. Just as a start I added the generic explosion sparks. As with every other effect, if you don't like it you can turn it off in the menu. + +bunch of renzokus fired at a wall + + + + +# Old style update - 02/08/2001 by Yrgol + +changes since the thing we sent to the news sites: +* lowered renzoku cost to 100 +* increased renzoku missile speed to 8000. +* increased renzoku radius to 120. +* lowered renzoku firing speed to 200ms. +* put in a 1 second delay before you can zanzoken out of stun. +* increased SBC radius to 150. +* increased SBC damage from 35%*mult to 40%*mult. +* increased mouthbeam damage from 20% to 30% (effective 60% vs same PL). +* lowered final flash damage from 40%*mult to 25%*mult. +* increased the angry gohan range from 500 to 1000. +* tweaked the angry gohan aura scaling. +* lowered angry gohan ki cost from 200 to 150. +* fixed a bug that made other people's charging flashes not show up. +* fixed the lagometer position +* made an exception so the death ball can compete with beams in collisions. +* fixed a bug that made third person extend into spectator free mode. +* fixed a horrible bug that made players not cycle in 1v1. +* tweaked some LMS code. +* fixed a bug that made the blocking anim play a bit too long. +* fixed the problem with the angry gohan shader. +* added basic power struggles. +* redid kiaiho. +__ +* took away boost speed bonus while _firing_ a beam. +* lowered kamehameha damage from 40%*mult to 30%*mult. +* upped final flash damage from 25%*mult to 30%*mult. +* made deathball work in power struggles. +* put the angry gohan cost back the way it was. +* lowered angry gohan range to 800 from 1000. +* slowed recharge time to 6 seconds. +__ +* added the new gohan and krillin. +* added some attack charge sounds. +* redid the timing on masenko and gave it a HA sound. +* fixed a bunch of post-death bugs. +* removed the generic flash sounds for kamehameha and masenko. +* added a new sprite for final flash. +__ +* added sprites for the big bang, sokidan, masenko, kamehameha, and renzoku. +* made oozaru picking more random. +* fixed the oozaru extendo penis. +* totally redid flight physics. +* changed up and down orientation while flying to be relative to the player's axis. +* added cg_flytilt for tilting the viewpoint when boost flying. +* fixed a bug with zanzoken going through clip brushes. +* increased the size of the smoke ring around explosions. +* added cg_explosionSmoke to turn off the smoke ring. +* redid jump. +* added wall jumps. +* added a short delay between jumps excluding wall jumps. avoiding people looking like bunnies. +* fixed the "slightly sticky ground" flight bug. +* added ctf, but it's not selectable from the menu. +* added more dramatic transformations. they are 5 seconds long and you are invuln during it. + + + + +# Random updates - 02/04/2001 by Yrgol + +Ansel's been making a lot of sprites to replace some of our attack models. Flight physics was totally redone to include momentum. + +CTF mode was added back in, but I don't know if it's going to be a fully supported mode or not. CTF is kinda goofy when you can fly fast, and we don't have any maps for it. CTF should not be confused with capture the dragonballs, which still isn't in phase 1. + + + + +# Power Struggles - 01/10/2001 by Yrgol + +Power struggles are in phase 1 now. Ki recharge time has been increased to 6 seconds for now, subject to change at any given moment. We had it take about 20-30 seconds a long time ago and it really killed the gameplay. Lots of attacks have gone through some rebalancing. + + + +# Yrgol’s Important Bid For Power Phase 1 README!!! - 01/10/2001 by Yrgol + +(complete with 3 different exclamation points!) +A wise man once said: “Those who do not read this document will suck at BFP.” + +What is Bid For Power? +Bid For Power (BFP) is a quake 3 total conversion. We have taken quake 3 and used it to create a new game. In order to play BFP, you will need the full retail version of Quake 3 Arena by ID Software, but once you have that, our game is a free downloadable add-on. + +In phase 1 of BFP, we place 6 of the fighters from the Freeza saga in an arena in order to fight. Power is gained over time, but the way to get power quickly is to kill other players. Obviously in the show, Krillin would not be able to fight Freeza, but if we followed that rule who would play Krillin? In BFP, the only difference between the characters is the set of attacks available. + +Features: +- 6 different selectable characters, each with 5 attacks. Goku, Piccolo, Vegeta, Freeza, Gohan, and Krillin. +- 21 different ki attacks including controllable attacks, homing attacks, and chargeable attacks (no guns!). +- Toggleable flight. +- offhand melee combat +- Re-"charge"able ki energy for ammo, flight, and speed/power boost. +- Power level determines the strength of attacks, speed of movement, max health, max ki, ki charge rate, and what attacks are available. +- 5 different game modes: FFA, 1v1, Team DM, Last Man Standing Team DM, and Oozaru mode. +- Numerous new player animations. +- Plenty of cool effects: head and full body transformations, selectable aura, ki trail, scalable attacks, big explosions, playable third person mode, first person vis mode. + +Installation (windows): +- Install the full retail version of Quake 3 Arena. The demo version will NOT work with BFP. +- Install the 1.27 point release for Quake 3 Arena, found at http://www.quake3world.com/files/patch127g.html +- Unzip the bfprc1.zip file into your quake 3 arena directory. Make sure to use folder names when extracting the files. +- The directory "bfp" will be created as a subdirectory of the quake 3 directory. +- To start the game, either use the "Mods" tab in quake 3, or start the game with command line args “+set fs_game bfp”. Creating a shortcut to quake 3 with that argument is the recommended way of starting BFP. + +Starting BFP:
+There are two ways to start BFP. The easiest way is to use the mods menu in Quake 3. From the main Quake 3 menu, click "mods". Then find "Dragon Ball Z: Bid For Power" on the list and highlight it. If you have a lot of mods installed, you may need to scroll the list in order to make BFP visible. Once you have BFP highlighted, click on the load button and BFP will start. + +The other way to start BFP is to use the command line arguments `+set fs_game bfp`. An easy way to do this under Windows is to create a shortcut. Highlight the quake3 icon in whatever directory you installed Quake 3 into, and press ctrl-c to copy the link. Press ctrl-v to create a shortcut to quake3. Right click on the new shortcut and go into the properties menu. Under the `shortcut` tab of the properties menu, change the target to `C:\Quake 3 Arena\quake3.exe +set fs_game bfp` if you installed Quake 3 into C:\Quake 3 Arena. Use this new shortcut to start BFP. + +BFP loads its own config file called 'bfp.cfg'. This allows either way of starting BFP to work without any config file problems. + +What is Phase 1? +Phase 1 is the first release of BFP. It covers important characters and places from the Freeza saga of DBZ. + +What is Phase 2? +Phase 2 will be the second major release of BFP. It will be released when it is done, and will cover important characters and places from the Android/Cell saga. Phase 2 will also likely have updated effects and game play. There will more than likely be a few patches between phase 1 and phase 2. + +How do I play a multi-player game? +This game is designed to be played multiplayer. From the main menu, click on the "multiplayer" button. BFP will scan all of the quake 3 arena servers and display any BFP servers that it finds. Once a BFP server is found, highlight it, click accept and the game will connect to the server. + +Is there a single-player game? +Just barely. This game is intended to be played multi-player. A single player game can be played with the bots, but we recommend playing against real people. + +Viewpoint +Viewpoint is how you see the game while you play it. You have your choice of 3 different viewpoints for playing BFP. To switch between the view, use the BFP Options menu. + +- Third Person: The model is seen from the outside. Unlike the standard quake 3 third person view, in BFP the crosshair is visible, and the angle is changed to create less blocking of the view. Cvars cg_thirdPersonAngle and cg_thirdPersonRange have had their cheat protection removed, and cg_thirdPersonHeight was added for additional customization. + +- First Person Vis: First person where your own model is drawn. Your viewpoint is positioned on the model's eyes, and you can see your own arms and legs while fighting. + +- First Person: The standard first person quake 3 view. + +Power Level + +Power level is how strong you are. Power level determines max health, max ki, movement speed, damage caused by attacks, and which attacks are available to use. The only way to get more power is to kill someone. When you die (in most game modes), your power level is reset to the average of all current power levels. When the game ends or the server restarts, the power level of the players is reset to the default. + +Power Tiers + +Power level is divided into tiers. While most power level effects are based on the power level itself, aura color and attacks available is determined by the tier. + +- Tier 1: < 100,000 PL. Blue aura, only one ki attack available. +- Tier 2: 100,000 - 250,000 PL. Red aura, two ki attacks selectable. +- Tier 3: 250,000 - 500,000 PL. Red aura, three ki attacks selectable. +- Tier 4: 500,000 - 999,000 PL. Red aura, four ki attacks selectable. +- Ultimate Tier: 1 mil PL. Yellow aura, all attacks selectable. + +Transformations + +At 1 million power level, the transformation happens. The aura color turns yellow, and if the character has a transformation it occurs. Saiyajin characters turn into Super Saiyajins. Other than the benefits from increased power level, there is no additional benefit of the transformation. + +Ki Energy + +Ki is your stamina. Ki is used as ammunition for the ki attacks. Additionally, ki can be used to increase power, speed, jumping height, and special attacks by using the boost key. Ki can be charged in order to replenish it at any time by holding down the charge key. Beware, while charging you can not move or attack, and are highly visible to other players. + +Flight + +BFP has a toggle able flight button. The default is "f" but it can be set in the controls menu. To begin flying, simply press f once, and once again to stop flying. While flying, you are able to move to wherever you look. The jump key will make you move up, and the crouch key will make you move down. Your ki drains at a steady rate. At low power levels you will need to limit your flight, while at high power levels you can fly all day. A large speed boost is gained by holding down the boost key. + +Melee Combat + +To engage in melee combat, hold down the melee key and put the crosshairs over your opponent. If you are close range, then you will start beating on him. If you are long range, you will dive at the bad guy for a strike. To add extra knock back and stun your opponent, hold down the boost key while melee fighting. + +Hit Stun + +While in hit stun, the player can not move, attack, block, or charge. 1 second of hit stun is added when the player runs out of ki. 3 seconds of hit stun is added when melee is used in combination with the boost key. Do not overlook this feature. Being able to knock people into hit stun with ki-boosted melee is one of the keys to being good at BFP. +*The melee stun can be avoided by holding melee while you are attacked. + +Zanzoken + +Zanzoken is a short, extremely fast movement. In BFP, zanzoken is a controlled short-range teleportation that is performed by double-tapping right or left movement keys. Zanzoken is useful for dodging attacks, and can be used to break out of hit stun after 1 second. + +Blocking + +Pressing block once grants the player between one and two seconds of blocking. Blocking drains ki quickly, but transfers all damage to ki instead of health. Blocking can also be used to deflect missile attacks. There is a one second delay between when the player stops blocking and when the player can block again. + +Ki Attacks + +Each character has 5 attacks. At the lowest power tier, the player only has one selectable attack. Each time the player advances to a new power tier, he gets a new selectable attack. By default, each of the numeric keys, 1-5, choose a different attack. Only one attack can be selected at a time, but switching between attacks is instant. + +- standard attacks: hold down fire and you fire. +- charge-up attacks: hold down fire to start charging the attack, release fire to fire. +- minimum charge-up attacks: same as charge-up attacks, but with a minimum charge-up time. The ball of ki will appear in the player's hands once he is prepared to fire. +- beam attacks: a type of charge-up attack. Can only fire one beam at a time. After firing, you can aim the attack by moving the mouse, or detonate it by hitting fire again. +- homing attacks: these attacks will home in on the nearest opponent. + +Attack Collisions + +Unlike quake 3 attacks, BFP attacks are more than just a trajectory. Attacks have actual dimensions, and will explode on contact with players. If an attack looks 50’ wide, it will hit any players who come within 25’ of the center of the attack. Mid-air attack collisions also happen. Two attacks that collide will damage each other, and the stronger attack will cause the other to explode. The exception is any beam attack, which will destroy any other attack that it makes contact with. + +Power Struggles + +When two beam attacks collide, a power struggle will happen. Instead of the stronger beam blowing up the weaker beam, the stronger beam will push back the weaker beam until it hits the other player. Ki boost can be used to up the power of your beam if you are losing a power struggle. The Death Ball is an exception among the missile attacks, and can be used in a power struggle. + +Krillin + +- Kikou: standard weak ki blast. Press or hold fire to fire a blast. +- Taiyoken: the solar flare. Hold down fire until charged, then release to blind people around you. +- Sokidan: homing ball. Hold down fire until charged, then release to fire. +- Kakusan: a missile that spawns 4 homing balls when detonated. Hold down fire until charged, and release to fire the missile. Press fire again to detonate the missile into 4 homing balls. +- Kienzan: the destructo disk. Hold down fire until charged, and release to fire. This attack goes through walls and does massive damage. + +Vegeta + +- Kikou: standard weak ki blast. Press or hold fire to fire a blast. +- Bakuhatsuha: larger ki blast. Press or hold fire to fire a blast. +- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. +- Big Bang Attack: a beam that has a massive explosion. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate into a massive big bang explosion. +- Final Flash: a large and powerful beam attack. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. + +Gohan + +- Kikou: standard weak ki blast. Press or hold fire to fire a blast. +- Sokidan: homing ball. Hold down fire until charged, then release to fire. +- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. +- Masenko: beam attack. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. +- Energy Dan (Angry Gohan Attack): Gohan creates a massive and continuous explosion around himself. Press and hold fire. After a few seconds the explosion will appear. Gohan can not move while performing this attack. + +Piccolo + +- Kikou: standard weak ki blast. Press or hold fire to fire a blast. +- Eye Beam: a continuous blast of power from the eyes. Press and hold fire. +- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. +- Chobakuretsumaha: a fast and powerful homing attack. Hold down fire until charged, and release to fire. +- Special Beam Cannon: a fast and narrow but extremely powerful spiraling beam. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. + +Goku + +- Kikou: standard weak ki blast. Press or hold fire to fire a blast. +- Kiaiho: An invisible blast of energy from the hands with a massive knockback. Press or hold fire to attack. +- Sokidan: homing ball. Hold down fire until charged, then release to fire. +- Renzoku Energy Dan: multiple fast-firing ki blasts. Hold down fire to attack. +- Kamehameha: beam attack. Hold down fire until charged, release to fire. While firing, use the cursor to aim the attack. Press fire again to detonate the attack. + +Freeza + +- Kikou: standard weak ki blast. Press or hold fire to fire a blast. +- Shyogeki Ha: fast firing, extremely fast moving ki blasts from the fingers. Hold fire to attack. +- Freeza Beam: an extremely fast ki attack fired from the finger. Press or hold fire to fire a blast. +- Tsuibi Kienzan: Freeza’s homing version of the kienzan. Hold down fire until charged, and release to fire. This attack goes through walls. +- Death Ball: a gigantic ball of ki. Hold down fire until charged, and release to fire. This attack is huge in size and does massive damage. + +Game Types: + +- Death Match: Players start at ~50,000 PL. Additional PL is gained for each kill. On death, the PL of the player resets to the average of all other PLs. +- Tourney: Same rules as DM, but with only two players at a time. +- Team DM: Same rules as DM, but with teams. +- Team LMS: Last Man Standing. If you’ve ever played clan arena you should know this mode. Players start at 1 mil PL, and players who die can not respawn until the next round. +- Oozaru: This game works as regular FFA, except one player is chosen to be the ~50’ tall Oozaru. The player who kills the Oozaru gets to be the Oozaru next. The Oozaru has extra power and speed, and is also a giant compared to the other players. + +CVARS: + +If you don't already know what a cvar is you should probably ignore this section. These can be used from the q3 console to access features. Most of these features can be set from within the menu. + +* g_basepl [1-999]: set the starting power level from one thousand to one mil. +* cg_kitrail [0-99]: set the length of the ki trail. 0 turns it off. +* cg_crosshairhealth [0/1]: turn on or off the feature that makes the crosshair turn red when you score a hit. +* cg_lightauras [0/1]: turn on or off the aura dynamic lights. +* cg_lightexplosions [0/1]: turn on or off the explosion dynamic lights. +* cg_drawKIWarning [0/1]: turn on or off the low ki warning. +* cg_chargeupAlert [0/1]: turn on or off the "ready" message when charging attacks. +* cg_stfu [0/1]: disable character voices when firing attacks. +* cg_lowpolysphere [0/1]: force the use of a lower polycount sphere. +* cg_explosionShell [0/1]: turn on or off the explosion shell. +* cg_drawOwnModel [0/1]: toggle first person between traditional and vis modes. + +Help my computer can’t handle BFP! + +With all of the graphics options turned on, BFP takes a much beefier system than standard Quake 3. If your computer sucks, don’t despair. We’re looking out for you. BFP is meant to push a p3 500 with 128 megs of memory and a GeForce video card, but it is designed to be able to run playably on a celeron 300 with 128 megs of memory and a TNT2. However, with a much weaker system than that, you are pushing your luck with BFP. + +Here are some things you can do to try to boost your performance. + +- The first thing to try is turning off dynamic lights in the game options menu. Quake 3 can be slow when it comes to handling dynamic lights, and there are a lot of them in BFP. +- Go into the BFP options menu and use the ki trail slider bar to lower the length of the ki trail. Few systems can handle the ki trail at its max length without framerate loss. With the slider bar all the way to the left, the ki trail will have 0 length. With the slider bar all the way to the right, the ki trail will almost stretch the length of our maps. +- Go under the systems setting menu and lower the resolution to 640x480. Don’t gag, most Quake 3 competition players seem to use this resolution. +- Go into the BFP options menu and set the aura type to “polygonal aura”. This is the least costly aura type. Using the BFP options menu, turn transformation aura off, dynamic aura lights off, dynamic explosion lights off, ki trail off, small own aura on, and ssj perma glow off. With these settings BFP should run about as well as quake 3 does on your computer. +- If you are really having trouble go into the video set-up menu and change the lighting from lightmap to vertex. Everything will look crappy, but the game will run faster. +- If your system is running low on memory, use the BFP options menu to turn Force Default Skins on. This is the BFP equivalent of cg_forceModels in Quake 3. Force Default Skins will disable all custom skins for the models. With this option on, there will only be one skin shared between all Vegetas. + +Who made this game, and why is it free? + +Bid For Power was made by a group of volunteers. We’re all DBZ fans and most of us have watched all or nearly all the Japanese episodes. I can’t speak for all the reasons of the individual team members, but we all thought the idea of a first person DragonBall Z game was cool enough to want to get involved. It’s free because we are not a company, and don’t have the money to pay off people like Id. This game was created by people, for fun, in their spare time. Everyone on the team contributed to the game design and art style. + +- Chris James: project lead, coordinator, original concept, PR. +- Yrgol: programming, game design. +- Rodney: Goku and Piccolo models, animation, additional model tweaking, 2d art, skins. +- Pyrofragger: Gohan, Krillin, and SSJ Goku models, animation, DNamek map, aura and attack models. +- NilreMK: Phase 2 and “secret character” models, animation, attack models, Q3 editing guru. +- Mooky: Vegeta and Freeza models. +- Ansel: lead skin artist, 2d art +- Dethayngel: skins. +- Ebola: skins. +- Anthony: 2d art, char select art. +- Badhead: attack and aura shaders. +- Number17: sounds. +- Yngwei: Snake Way map, skyboxes, performed all music used in BFP. +- GangstaPoodle: GPCity, GPNamek, GPCityDark, and GPCave maps and textures, 2d art. +- Kit Carson: Earth and NamekXL maps and textures. +- $onik: Kami’s Lookout map textures. +- Perfect Chaos: additional map textures. +- Nat: webmaster, additional programming. +- Disco Stu: web design + + + + +# zanzoken - 12/17/2000 by Yrgol + +Zanzoken is in now. I'm doing it as a short range controlled-direction teleport. Double tap right or left in combination with other movement in order to zanzoken. I don't want to get into specifics but yes there are restrictions and costs to prevent this from being used all the time. + + + +# stuff - 12/14/2000 by Yrgol + +As you can tell I'm still making the mod. The bots own now. If all that mattered were code, bfp would be ready to release. Everything that I said was going to be in phase 1 is in there now and then some, and the bugs are minimal. A few of our models still need some tweaking before the whole is releasable. + +Funimation did indeed send us a cease and decist letter within days of selling dbz game rights to Infogrames. However, they have decided to take a closer look at the situation. We did not want to jump the gun on being foxed, because things are still fairly up in the air. We did not want the slashdot post to happen. + +Bid For Power will be released. It will most likely have Dragon Ball Z characters in it when it is released. + +17-3 (not finished): +* made bots aim beams. +* made bots charge attacks randomly above the minimum. +* made bots melee. +* made bots act blind when hit with the taiyoken. +* made bots aware of splash damage on charge-up attacks. +* lowered shyogeki ha damage from 20 to 5. +* lowered shyogeki ha range from 8192 to 1200. +* lowered shyogeki ha spread. +* made homing attacks easier to dodge. +* lowered kiaiho range from 8192 to 1000. +* made kiaiho do a stable 20% damage. +* removed the damage boost for ki boosting. +* got rid of the character change messages because they never worked right. +* shrunk the pre-fire deathball a bit so it doesn't clip freeza's head. +* made blindness not last through death. +* got rid of the melee axehandle smash direction. +* fixed a bug in melee that caused a crash. +* lowered hit-stun time from 3 seconds to 2. +* capped self-splash at 20%. +* added in the separate kienzan attack shader. +* shrunk freeza's kienzan. +* fixed a bug that made the kienzans stay around far longer than they should. +* fixed a bug that made kienzans hit way too fast and too often. +* gave the kienzans a max number of hits, which fixes a memory leak. +* fixed a bug with the animation timers. +* fixed a bug with switching weapons while firing a beam. +* fixed a bug with switching weapons while charging up an attack. +* made charging only prevent intentional movement, not glue you in place. + +17-2: +* made bots be 100% agressive all the time. +* made bots fly a better. +* taught bots how to use charge-up attacks and beams. +* fixed bot aim. +* upped the charge time for the chobakuretsumaha and sokidan. +* scaled down the pre-fire flashes of most attacks. +* fixed the spelling and grammer in the bot files and fixed the error messages. + +17-1: +* redid third person view. +* got around the gimble lock for flight so you can go upside down. +* changed default cg_thirdpersonheight to -50. + + + + +# more sutff than you wanted - 11/29/2000 by Yrgol + +16-3/17: +* fixed the timing of the "ready" message with the kamehameha. +* made a bfp.cfg file so that bfp will work fine by using the mods menu. +* fixed a bug that caused untextured weapon flashes to appear. +* fixed a bug that caused attack voices to sometimes come out at the wrong time. +* added cg_stfu to turn the voices off. +* fixed a bug that made melee mess up charge-up attacks. +* fixed a bug that caused a lot of problems in the char select screen. +* fixed a bug that made melee screw up the weapon time and hit way too fast. +* fixed a bug that made the charge-o-meter misread the sokidan/chobakuretsumaha power. +* fixed another bug with the ready message. +* added cull disable to all the explosion shaders so they don't disappear when you get too close. +* added an explosion shell/shockwave. can be turned off with cg_explosionShell 0. +* added cg_lowpolysphere to toggle between a low and a high polycount sphere model. +* fixed the infamous death-spawn bug. +* fixed the sizing of the accept button in the video setup menu. +* seemingly accidentally fixed the problem of ki trails showing through walls. +* made cg_thirdpersonrange and cg_thirdpersonheight get saved in bfp.cfg. +* gave cg_thirdpersonheight a default of 20. +* increased freezabeam explosion radius. +16-2: +* added g_basePL with a slider in the server startup to define starting PL. +* gave the renzoku an alternating firing offset. +* cut chobakuretsumaha damage in half. +* upped the ki cost of the chobakuretsumaha. +* lowered freezabeam damage. +* made melee hit stun not happen when both people are holding melee. +* made homing missiles select a target based on angle instead of distance. +* refined homing angle selection somewhat. +* fixed the sbc spiral so it disappears less often. +* fixed jump (detection and animation). +* made the kienzan stop disappearing when it hits other missiles. +* added self splash damage back in. +* made blocking deflect missiles. +* added in 17's explosion sounds. +* added in character sounds for sbc, kamehameha, final flash, bb attack, masenko. +* hopefully fixed the ride me big boy bug. + +16-1: +* made the spiral not disappear when two people sbc at the same time +* made ki trails a little more robust +* forced r_railCoreWidth to 15 on startup. +* removed a bug with the ssjhead getting an aura when it shouldn't. +* fixed a bug that caused the char select screen to crash the game. +* resized images so they were powers of 2. +* got rid of "unknown gametype" when connecting to a oozaru or lms server. +* homing attacks don't turn all the way around anymore. +* homing attacks no longer lose their velocity when homing. +* tweaked the various homing missile speeds to account for the new code. +* made taiyoken based on FOV, meaning you have to see it to go blind. +* set the size of menu buttons to 80x80 and made those values +settable through one file instead of 30 different files. +* made the eyebeam visible from all perspectives. +* upped the eyebeam damage. +* made the eyebeam not draw when holding melee. +* fixed a rotation problem on the auras. +* fixed a problem with showing the wrong gametype name when starting a server. + +16: +* decreased melee dive range from 2000 to 700. +* hopefully fixed the problem of melee dive teleporting you into objects. +* new menu. +* new attack icons. +* new hud. +* new ki trail. +* fixed the servers menu to differentiate between bfp gametypes. +* removed the "impressive" for the freezabeam. +* lowered the ki cost of the freezabeam from 1000 to 500. +* made the freezabeam explode when it hits a player instead of going through them. +* gave the freezabeam explosion some damage. +* changed the sound played for the freezabeam. +* fixed a bug in the eyebeam that made the beam not show up. +* changed the name of the sbc to makankosappo. +* removed drowning. +* made the speed boost depend on PL instead of power tier. +* made cg_kitrail be a slider var from 0 to 99 for ki trail length. + +15-8: +* removed heals from spawning on maps. +* increased visual explosion size. +* made charging a lot more effective. +* cut damage in half. +* made melee dive a lot more solid. +* gave the melee hit sound an origin. + +15-7: +* made the chargeup alert display in third person +* moved the chargeup alert to the bottom of the screen. +* plays the pain sounds based on percent of max health. +* upped masenko damage. +* made the hud player counter not include dead people. +* removed gibs for killing someone. you can still gib a corpse. +* made the prepare anim play for kakusan. +* fixed the timing on at least some charge up attack anims. +* added a flash model to firing beams. +* added KamehamehaFlashShader etc for the beam attacks. +this shader needs to be opaque enough to hide the beam origin +* scaled down the pre-fire ball for beam attacks. +* removed the standard flash entirely. + +15-6: +* added a "ready to fire" message for charge-up attacks. +* added cg_chargeupAlert to turn off that message. +* fixed a bug that would let attacks get kienzan properties. +* upped kakusan damage some. +* lowered taiyoken damage to 1%. +* added a visibility check on taiyoken. only checks barriers, not direction. +* increased taiyoken chargeup time. +* shortened blindness time some. +* made the melee_axehandle animation run, but it's not very noticable. +* toned down the angry gohan. +* made the prepare anim play for the angry gohan. +* changed the scoreboard name of the angry gohan to the Angry Gohan Attack. +* fixed "the incredible eternal kienzan" bug. + +15-5: +* first person vis mode works. it works best with models that have tag_eyes (gohan/krillin). +* angry gohan no longer draws while meleeing or charging. +* took out the announcer sounds for player names. +* blindness no longer stays with you through death. +* took out the ring model for explosions on players. +* fixed a scaling problem in the explosion ring that made it go black. +* removed the eject brass option from the game options menu. +* individual score now survives death in LMS mode. +* following in LMS spectator mode no longer fucks the next round. +* got rid of some red in the menu. +* gave explosions and missiles dynamic lights. +* added cg_lightExplosions to turn off dynamic explosion lights. +* removed the chargeup anim for taiyoken. + +15-4: lost all code since 15, had to redo stuff. +--SHADERS-- +* added shaders for kamehameha and masenko. +* added separate beam shaders. +* added separate shaders for explosions. +* added a shader for the freezabeam explosion +--WEAPONS-- +* freeza's second attack no longer ejects brass. +* fixed manual beam detonation. +* missiles should collide with each other a bit better. +* fixed homing attack collisions. +* increased eye beam range. +* removed self-damage. +* gave deathball a min charge up time (it was supposed to have one). +* gave masenko a min charge up time (it was supposed to have one). +* decreased the chargeup speed of sokidan/chobakuretsumaha. +* made all weapons explode when they hit a player. +* put the BIG BANG in the BIG BANG attack +* pumped up the deathball. +* can't continue firing a beam while charging, blocking, +melee fighting, or in hit-stun. +* beam attacks use the sphere as the pre-fire flash. +* beams now trump missiles for mid-air collisions. +* lowered renzoku damage. +* made taiyoken work. +* gave the SBC a spiral trail. +* weapon firing sound plays while firing beams instead of +while charging them. +* made the freeza beam red and gave it an explosion. +* gave explosions a smoke ring. +--OTHER-- +* custom skins now work fine. if you use a custom +vegeta skin that I don't have, instead of showing +up as sarge you will show up as vegeta/default on +my screen. +* somewhat improved bots. +* cg_forceModel removed. +* added cg_forceSkin to force the default skin for each model. +* changed obituary messages. +* beam attacks no longer blind you in first person view. +* switched from the nuke model to the sphere model for explosions. +* tweaked explosions a lot. +* picking up a normal health after a mega health won't set +you back to 100% health anymore. +* no longer plays the melee sound for missiles. +* unfucked spectator mode. +* switching chars while in LMS mode causes a suicide like in +any other game mode. diff --git a/source/cgame/cg_consolecmds.c b/source/cgame/cg_consolecmds.c index fa6328a..a7f04e4 100644 --- a/source/cgame/cg_consolecmds.c +++ b/source/cgame/cg_consolecmds.c @@ -173,23 +173,44 @@ CG_StartOrbit_f */ static void CG_StartOrbit_f( void ) { + // BFP - No developer cvar handling +#if 0 char var[MAX_TOKEN_CHARS]; trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); if ( !atoi(var) ) { return; } +#endif if (cg_cameraOrbit.value != 0) { trap_Cvar_Set ("cg_cameraOrbit", "0"); - // trap_Cvar_Set("cg_thirdPerson", "0"); // BFP - don't disable even when restarting the server + trap_Cvar_Set ("cg_thirdPerson", "0"); } else { trap_Cvar_Set("cg_cameraOrbit", "5"); + // trap_Cvar_Set("cg_cameraOrbitDelay", "50"); trap_Cvar_Set("cg_thirdPerson", "1"); trap_Cvar_Set("cg_thirdPersonAngle", "0"); - trap_Cvar_Set("cg_thirdPersonRange", "110"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); } } + +/* +================== +CG_TransformOrbit_f +================== +*/ +static void CG_TransformOrbit_f( void ) { // BFP - Transformation orbit + // just force enabling camera orbit with these values + if ( cg_cameraOrbit.integer != 5 ) { // c'mon, force it! (>_<) + trap_Cvar_Set("cg_cameraOrbit", "5"); + cg_cameraOrbit.integer = 5; + } + trap_Cvar_Set("cg_cameraOrbitDelay", "50"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); +} + /* static void CG_Camera_f( void ) { char name[1024]; @@ -232,8 +253,9 @@ static consoleCommand_t commands[] = { { "vtell_attacker", CG_VoiceTellAttacker_f }, { "tcmd", CG_TargetCommand_f }, { "startOrbit", CG_StartOrbit_f }, + { "transformorbit", CG_TransformOrbit_f }, // BFP - Transformation orbit //{ "camera", CG_Camera_f }, - { "loaddeferred", CG_LoadDeferredPlayers } + { "loaddefered", CG_LoadDeferredPlayers } }; @@ -307,5 +329,5 @@ void CG_InitConsoleCommands( void ) { trap_AddCommand ("teamvote"); trap_AddCommand ("stats"); trap_AddCommand ("teamtask"); - trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo + trap_AddCommand ("loaddefered"); } diff --git a/source/cgame/cg_cvar.h b/source/cgame/cg_cvar.h index 84c902b..6c89c62 100644 --- a/source/cgame/cg_cvar.h +++ b/source/cgame/cg_cvar.h @@ -31,14 +31,19 @@ CG_CVAR( cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE ) CG_CVAR( cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE ) +CG_CVAR( cg_simpleHUD, "cg_simpleHUD", "0", CVAR_ARCHIVE ) // BFP - Simple HUD +CG_CVAR( cg_chargeupAlert, "cg_chargeupAlert", "1", CVAR_ARCHIVE ) // BFP - Ready message when charging attacks CG_CVAR( cg_crosshairColor, "cg_crosshairColor", "7", CVAR_ARCHIVE ) // BFP - Crosshair color CG_CVAR( cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE ) CG_CVAR( cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE ) CG_CVAR( cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE ) CG_CVAR( cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE ) +CG_CVAR( cg_lifedeaths, "cg_lifedeaths", "0", CVAR_ROM | CVAR_ARCHIVE ) // BFP - Number of deaths +CG_CVAR( cg_lifekills, "cg_lifekills", "0", CVAR_ROM | CVAR_ARCHIVE ) // BFP - Number of kills CG_CVAR( cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE ) CG_CVAR( cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE ) CG_CVAR( cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE ) +CG_CVAR( cg_particles, "cg_particles", "1", CVAR_ARCHIVE ) // BFP - Particles CG_CVAR( cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE ) CG_CVAR( cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE ) CG_CVAR( cg_gun_frame, "cg_gun_frame", "", CVAR_ROM ) @@ -64,7 +69,17 @@ CG_CVAR( cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT ) CG_CVAR( cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT ) CG_CVAR( cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT ) CG_CVAR( cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT ) +CG_CVAR( cg_lowpolysphere, "cg_lowpolysphere", "0", CVAR_ARCHIVE ) // BFP - Low poly sphere +CG_CVAR( cg_bigExplosions, "cg_bigExplosions", "1", CVAR_ARCHIVE ) // BFP - Big explosions +CG_CVAR( cg_explosionRing, "cg_explosionRing", "1", CVAR_ARCHIVE ) // BFP - Explosion ring +CG_CVAR( cg_explosionShell, "cg_explosionShell", "1", CVAR_ARCHIVE ) // BFP - Explosion shell +CG_CVAR( cg_explosionSmoke, "cg_explosionSmoke", "1", CVAR_ARCHIVE ) // BFP - Explosion smoke +CG_CVAR( cg_lightExplosions, "cg_lightExplosions", "1", CVAR_ARCHIVE ) // BFP - Dynamic explosion lights CG_CVAR( cg_flytilt, "cg_flytilt", "1", CVAR_ARCHIVE ) // BFP - Fly tilt +CG_CVAR( cg_kiTrail, "cg_kiTrail", "50", CVAR_ARCHIVE ) // BFP - Ki trail length +CG_CVAR( cg_beamTrail, "cg_beamTrail", "50", CVAR_ARCHIVE ) // BFP - Beam trail length +CG_CVAR( cg_playHitSound, "cg_playHitSound", "0", CVAR_ARCHIVE ) // BFP - Play hit sound +CG_CVAR( cg_stfu, "cg_stfu", "0", CVAR_ARCHIVE ) // BFP - Shut the f*** up! CG_CVAR( cg_thirdPersonRange, "cg_thirdPersonRange", "110", CVAR_ARCHIVE ) // BFP CG_CVAR( cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_ARCHIVE ) // BFP CG_CVAR( cg_thirdPersonHeight, "cg_thirdPersonHeight", "-60", CVAR_ARCHIVE ) // BFP - Camera height @@ -72,13 +87,25 @@ CG_CVAR( cg_fixedThirdPerson, "cg_fixedThirdPerson", "1", CVAR_ARCHIVE ) // BFP CG_CVAR( cg_drawOwnModel, "cg_drawOwnModel", "1", CVAR_ARCHIVE ) // BFP - toggle first person between traditional and vis modes CG_CVAR( cg_drawKiWarning, "cg_drawKiWarning", "1", CVAR_ARCHIVE ) // BFP - Ki warning CG_CVAR( cg_stableCrosshair, "cg_stableCrosshair", "0", CVAR_ARCHIVE ) // BFP - Accurate crosshair +CG_CVAR( cg_spriteAura, "cg_spriteAura", "0", CVAR_ARCHIVE ) // BFP - Sprite aura +CG_CVAR( cg_particleAura, "cg_particleAura", "0", CVAR_ARCHIVE ) // BFP - Particle aura +CG_CVAR( cg_lightAuras, "cg_lightAuras", "1", CVAR_ARCHIVE ) // BFP - Light auras +CG_CVAR( cg_smallOwnAura, "cg_smallOwnAura", "0", CVAR_ARCHIVE ) // BFP - Small own aura +CG_CVAR( cg_lightweightAuras, "cg_lightweightAuras", "0", CVAR_ARCHIVE ) // BFP - Lightweight auras +CG_CVAR( cg_polygonAura, "cg_polygonAura", "1", CVAR_ARCHIVE ) // BFP - Polygonal aura +CG_CVAR( cg_highPolyAura, "cg_highPolyAura", "1", CVAR_ARCHIVE ) // BFP - High polycount aura +CG_CVAR( cg_permaglowUltimate, "cg_permaglowUltimate", "1", CVAR_ARCHIVE ) // BFP - Ultimate perma-glow +CG_CVAR( cg_transformationAura, "cg_transformationAura", "1", CVAR_ARCHIVE ) // BFP - Transformation aura CG_CVAR( cg_thirdPerson, "cg_thirdPerson", "1", 0 ) // BFP +CG_CVAR( cg_superdeformed, "cg_superdeformed", "0", CVAR_ARCHIVE ) // BFP - Super Deformed (Chibi style) easter egg CG_CVAR( cg_yrgolroxor, "cg_yrgolroxor", "0", 0 ) // BFP - Yrgol Roxor easter egg +CG_CVAR( cg_3dparticles, "cg_3dparticles", "0", CVAR_ARCHIVE ) // BFP - 3D particles CG_CVAR( cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE ) CG_CVAR( cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE ) -CG_CVAR( cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE ) +CG_CVAR( cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE ) // BFP - TODO: In the future, remove cg_forceModel, which wasn't removed originally? +CG_CVAR( cg_forceSkin, "cg_forceSkin", "0", CVAR_ARCHIVE ) // BFP - Forces the default skin for each model, applicable when changing models/skins CG_CVAR( cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE ) -CG_CVAR( cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE ) +CG_CVAR( cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE ) // BFP - TODO: In the future, remove cg_deferPlayers, which wasn't removed originally? CG_CVAR( cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE ) CG_CVAR( cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO ) CG_CVAR( cg_stats, "cg_stats", "0", 0 ) @@ -108,10 +135,15 @@ CG_CVAR( cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE ) CG_CVAR( cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE ) CG_CVAR( cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE ) CG_CVAR( cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE ) + +CG_CVAR( cg_oldRocket, "cg_oldRocket", "0", CVAR_ARCHIVE ) +/* +// BFP - Remove these unused cvars from the code in the future CG_CVAR( cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE ) -CG_CVAR( cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE ) CG_CVAR( cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE ) CG_CVAR( cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE ) +*/ + //CG_CVAR( cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE ) #undef CG_CVAR diff --git a/source/cgame/cg_draw.c b/source/cgame/cg_draw.c index 7774b23..58ffdcb 100644 --- a/source/cgame/cg_draw.c +++ b/source/cgame/cg_draw.c @@ -155,11 +155,58 @@ void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t head float len; vec3_t origin; vec3_t mins, maxs; + // BFP - Powerlevel + int powerlevel = -1; + // BFP - Ultimate tier head model and skin + qhandle_t tierHeadModel, tierHeadSkin; + // BFP - Monster gamemode, check to show monster head model and icon + qboolean isMonster; ci = &cgs.clientinfo[ clientNum ]; + // BFP - Powerlevel + powerlevel = cg_entities[clientNum].currentState.frame; + if ( clientNum == cg.snap->ps.clientNum ) { // fixes a weird bug when trying to see the powerlevel of itself + powerlevel = cg.snap->ps.persistant[PERS_POWERLEVEL]; + } + + // BFP - Monster gamemode, check to show monster head model and icon + isMonster = ( cgs.gametype == GT_MONSTER && cgs.monster > 0 + && ( cg_entities[clientNum].currentState.eFlags & EF_MONSTER ) ); + + // BFP - Ultimate tier head model and skin + if ( isMonster ) { + tierHeadModel = cgs.media.monsterHeadModel; + tierHeadSkin = cgs.media.monsterHeadSkin; + if ( powerlevel >= 1000 && cgs.media.monsterUltTierHeadModel ) { + tierHeadModel = cgs.media.monsterUltTierHeadModel; + tierHeadSkin = cgs.media.monsterUltTierHeadSkin; + } + } else { + tierHeadModel = ci->headModel; + tierHeadSkin = ci->headSkin; + if ( powerlevel >= 1000 ) { + if ( ci->ultTierHeadModel ) { + tierHeadModel = ci->ultTierHeadModel; + } + if ( ci->ultTierHeadSkin ) { + tierHeadSkin = ci->ultTierHeadSkin; + } + } + } + if ( cg_draw3dIcons.integer ) { cm = ci->headModel; + // BFP - Ultimate tier head model + if ( isMonster ) { + cm = cgs.media.monsterHeadModel; + if ( powerlevel >= 1000 && cgs.media.monsterUltTierHeadModel ) + cm = cgs.media.monsterUltTierHeadModel; + } else { + cm = ci->headModel; + if ( powerlevel >= 1000 && ci->ultTierHeadModel ) + cm = ci->ultTierHeadModel; + } if ( !cm ) { return; } @@ -178,9 +225,14 @@ void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t head // allow per-model tweaking VectorAdd( origin, ci->headOffset, origin ); - CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); + // BFP - Ultimate tier head model and skin draw + CG_Draw3DModel( x, y, w, h, tierHeadModel, tierHeadSkin, origin, headAngles ); } else if ( cg_drawIcons.integer ) { - CG_DrawPic( x, y, w, h, ci->modelIcon ); + qhandle_t icon = ci->modelIcon; + if ( isMonster && cgs.media.monsterModelIcon ) { + icon = cgs.media.monsterModelIcon; + } + CG_DrawPic( x, y, w, h, icon ); } // if they are deferred, draw a cross out @@ -226,8 +278,6 @@ void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean fo handle = cgs.media.redFlagModel; } else if( team == TEAM_BLUE ) { handle = cgs.media.blueFlagModel; - } else if( team == TEAM_FREE ) { - handle = cgs.media.neutralFlagModel; } else { return; } @@ -239,8 +289,6 @@ void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean fo item = BG_FindItemForPowerup( PW_REDFLAG ); } else if( team == TEAM_BLUE ) { item = BG_FindItemForPowerup( PW_BLUEFLAG ); - } else if( team == TEAM_FREE ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); } else { return; } @@ -303,7 +351,8 @@ static void CG_DrawStatusBarHead( float x ) { angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; - CG_DrawHead( x, 480 - size, size, size, + // BFP - Place the head HUD at the corner leaving a bit of margin + CG_DrawHead( x + 7, 472 - size, size, size, cg.snap->ps.clientNum, angles ); } @@ -314,7 +363,7 @@ CG_DrawStatusBarFlag ================ */ static void CG_DrawStatusBarFlag( float x, int team ) { - CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse ); + CG_DrawFlagModel( x + 147, 480 - ICON_SIZE - 63, ICON_SIZE, ICON_SIZE, team, qfalse ); // BFP - Before Q3: x: x, y: 480 - ICON_SIZE } /* @@ -344,6 +393,112 @@ void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) trap_R_SetColor( NULL ); } +// BFP - Gauge bar colors +vec4_t backgroundBlue = {0, 0, 1, 0.33}; +vec4_t barRed = {1, 0, 0, 0.66}; +vec4_t barGreen = {0, 1, 0, 0.66}; +vec4_t barBlue = {0, 0, 1, 0.66}; +vec4_t barYellow = {1, 1, 0, 0.66}; + +/* +================ +CG_DrawHealthGauge +================ +*/ +static void CG_DrawHealthGauge( float x, float y, float w, float h, int value, int maxValue ) { // BFP - Health gauge + if ( cg_simpleHUD.integer < 1 ) { // BFP - Simple HUD + float percentage = (float)value / (float)maxValue; + float barWidth = 2 * w * percentage; + float *barColor = barGreen; + + if ( (float)value <= (float)maxValue * 0.64 ) { // <= 64% of health + barColor = barBlue; + } + if ( (float)value <= (float)maxValue * 0.31 ) { // <= 31% of health + barColor = barRed; + } + CG_FillRect( x - w, y, w * 2, h, backgroundBlue ); + trap_R_SetColor( barColor ); + CG_DrawPic( x - w, y, barWidth, h, cgs.media.whiteShader ); + } +} + +/* +================ +CG_DrawKiGauge +================ +*/ +static void CG_DrawKiGauge( float x, float y, float w, float h, int value, int maxValue ) { // BFP - Ki gauge + if ( cg_simpleHUD.integer < 1 ) { // BFP - Simple HUD + float percentage = (float)value / (float)maxValue; + float barWidth = 2 * w * percentage; + float *barColor = barGreen; + + if ( maxValue > 9999 ) { // make the bar color notice at that small difference (don't change the percentage at all) + maxValue = 9999; + } + + if ( (float)value < (float)maxValue * (2.0f / 3.0f) ) { + barColor = barBlue; + } + if ( (float)value < (float)maxValue * (1.0f / 3.0f) ) { + barColor = barRed; + } + CG_FillRect( x - w, y, w * 2, h, backgroundBlue ); + trap_R_SetColor( barColor ); + CG_DrawPic( x - w, y, barWidth, h, cgs.media.whiteShader ); + } +} + +/* +================ +CG_DrawPowerlevelGauge +================ +*/ +static void CG_DrawPowerlevelGauge( float x, float y, float w, float h, int value, int maxValue ) { // BFP - Powerlevel gauge + if ( cg_simpleHUD.integer < 1 ) { // BFP - Simple HUD + float percentage = (float)value / (float)maxValue; + float barWidth = 2 * w * percentage; + float *barColor = barYellow; + + if ( (float)value < (float)maxValue * 0.5 ) { + barColor = barRed; + } + if ( (float)value < (float)maxValue * 0.1 ) { + barColor = barBlue; + } + CG_FillRect( x - w, y, w * 2, h, backgroundBlue ); + trap_R_SetColor( barColor ); + CG_DrawPic( x - w, y, barWidth, h, cgs.media.whiteShader ); + } +} + +/* +================== +CG_DrawSelectedKiAttack +================== +*/ +static void CG_DrawSelectedKiAttack( void ) { // BFP - Show selected ki attack + // BFP - TODO: Replace weapons to ki attack configs + // cg.weaponSelect frozen at whatever it initialized to, so the icon + // never reflected the actual active attack slot, particularly + // noticeable when spectating another player using a different slot than expected. + // cg.predictedPlayerState.weapon already tracks the correct player automatically + CG_DrawPic( 535, 383, 96, 37, cg_weapons[ cg.predictedPlayerState.weapon ].weaponIcon ); +} + +/* +================ +CG_DrawHUDOverlay +================ +*/ +static void CG_DrawHUDOverlay( void ) { // BFP - HUD overlay + if ( cg_simpleHUD.integer < 1 ) { // BFP - Simple HUD + CG_DrawPic( 5, 413, 211, 65, cgs.media.hudoverlay ); + CG_DrawPic( 430, 378, 206, 96.5, cgs.media.hudoverlayr ); + } +} + /* ================ CG_DrawStatusBar @@ -351,15 +506,17 @@ CG_DrawStatusBar ================ */ static void CG_DrawStatusBar( void ) { - int color; - centity_t *cent; playerState_t *ps; - int value; + int value, hvalue; char *string; - int spacer = CHAR_WIDTH*2; vec4_t hcolor; vec3_t angles; + // BFP - Unused variables +#if 0 + centity_t *cent; + int spacer = CHAR_WIDTH*2; vec3_t origin; + int color; static float colors[4][4] = { // { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; @@ -367,6 +524,11 @@ static void CG_DrawStatusBar( void ) { { 1.0f, 0.2f, 0.2f, 1.0f }, // low health { 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 +#endif + + // BFP - Gauge sizes + const float GAUGE_WIDTH = 57.5; + const float GAUGE_HEIGHT = 18; if ( cg_drawStatus.integer == 0 ) { return; @@ -375,7 +537,8 @@ static void CG_DrawStatusBar( void ) { // draw the team background CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] ); - cent = &cg_entities[cg.snap->ps.clientNum]; + // BFP - Unused cent variable + // cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; VectorClear( angles ); @@ -392,15 +555,10 @@ static void CG_DrawStatusBar( void ) { } #endif - // BFP - place head in the corner - CG_DrawStatusBarHead( 0 ); // CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); - if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED ); } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE ); - } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { - CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE ); } // BFP - hide armor and disable ammo text @@ -449,14 +607,38 @@ static void CG_DrawStatusBar( void ) { } #endif - // BFP - TODO: Draw a HUD bar of 6 points to indicate ki attack preparation - // BFP - TODO: Draw player powerlevel info - + // BFP - Draw player powerlevel info + value = ps->persistant[PERS_POWERLEVEL]; + CG_DrawPowerlevelGauge( 562.5, 452, GAUGE_WIDTH + 10, GAUGE_HEIGHT, value, 1000 ); + // BFP - NOTE: Just curious, BFP had a 16-bit size issue (only reaches 32767 - the maximum), + // they thought that adding ",000" at the last can be entertaining for the player + if ( value >= 1000 ) { + string = va( "1 Mil!" ); + CG_DrawBigString( 510, 454, string, 1.0f ); + } else { + int x = 510; + string = va( "%d", value ); + if ( value < 10 ) { + x = 526; + } + CG_DrawBigString( x, 454, string, 1.0f ); + string = va( ",000" ); + x = 552; + if ( value < 100 ) { + x = 538; + } + CG_DrawBigString( x, 454, string, 1.0f ); + } // // health // - value = ps->stats[STAT_HEALTH]; + // BFP - Visualize only 1 - 100 instead 0 - 1000 + value = ( ps->stats[STAT_HEALTH] * 100 ) / ps->stats[STAT_MAX_HEALTH]; + hvalue = ( value < 1 ) ? 1 : ( value > 100 ) ? 100 : value; + CG_DrawHealthGauge( 154.5, -18 + SCREEN_HEIGHT - ( SMALLCHAR_HEIGHT * 2 ), GAUGE_WIDTH, GAUGE_HEIGHT, hvalue, 100 ); + // BFP - No drawing HP Q3 field +#if 0 if ( value > 100 ) { trap_R_SetColor( colors[3] ); // white } else if (value > 25) { @@ -469,10 +651,11 @@ static void CG_DrawStatusBar( void ) { } // stretch the health up when taking damage - // CG_DrawField ( 185, 432, 3, value); + CG_DrawField ( 185, 432, 3, value); +#endif - string = va( "HP: %d%%", value ); // %% is a percentage sign - CG_DrawBigString( spacer, SCREEN_HEIGHT - ( SMALLCHAR_HEIGHT * 2 ), string, 1.0f ); + string = va( "%d%%", hvalue ); // %% is a percentage sign + CG_DrawBigString( 107, -18 + SCREEN_HEIGHT - ( SMALLCHAR_HEIGHT * 2 ), string, 1.0f ); CG_ColorForHealth( hcolor ); trap_R_SetColor( hcolor ); @@ -480,17 +663,18 @@ static void CG_DrawStatusBar( void ) { if ( ps->stats[STAT_KI] <= 0 ) // BFP - If ki is less than 0, adjust to 0 ps->stats[STAT_KI] = 0; value = ps->stats[STAT_KI]; - string = va( "KI: %d", value ); - CG_DrawBigString( spacer, SCREEN_HEIGHT - SMALLCHAR_HEIGHT, string, 1.0f ); + CG_DrawKiGauge( 154.5, -12 + SCREEN_HEIGHT - SMALLCHAR_HEIGHT, GAUGE_WIDTH, GAUGE_HEIGHT, value, ps->stats[STAT_MAX_KI] ); + + string = va( "%d", value ); + CG_DrawBigString( 107, -12 + SCREEN_HEIGHT - SMALLCHAR_HEIGHT, string, 1.0f ); +// BFP - disable armor +#if 0 if (value >= 100) { trap_R_SetColor( colors[3] ); // white } else { trap_R_SetColor( colors[1] ); // red } - -// BFP - disable armor -#if 0 // // armor // @@ -788,6 +972,7 @@ static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { // draw weapon icon xx += TINYCHAR_WIDTH * 3; + // BFP - TODO: Replace weapons to ki attack configs if ( cg_weapons[ci->curWeapon].weaponIcon ) { CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, cg_weapons[ci->curWeapon].weaponIcon ); @@ -880,6 +1065,14 @@ static float CG_DrawScores( float y ) { vec4_t color; float y1; gitem_t *item; + const int POS_STR_Y = -19; // BFP - Before Q3: 4 + const int POS_FLAG_X = 6; // BFP - Before Q3: (nothing) + const int POS_FLAG_Y = 34; // BFP - Before Q3: 4 + + // BFP - Don't show small two score display when spectating + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return 0; + } s1 = cgs.scores1; s2 = cgs.scores2; @@ -890,7 +1083,7 @@ static float CG_DrawScores( float y ) { // draw from the right side to left if ( cgs.gametype >= GT_TEAM ) { - x = 640; + x = 630; // BFP - Before Q3: 640 color[0] = 0.0f; color[1] = 0.0f; color[2] = 1.0f; @@ -898,11 +1091,11 @@ static float CG_DrawScores( float y ) { s = va( "%2i", s2 ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_FillRect( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, color ); if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + CG_DrawPic( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } - CG_DrawBigString( x + 4, y, s, 1.0F); + CG_DrawBigString( x + 4, y - POS_STR_Y, s, 1.0F ); if ( cgs.gametype == GT_CTF ) { // Display flag status @@ -911,7 +1104,7 @@ static float CG_DrawScores( float y ) { if (item) { y1 = y - BIGCHAR_HEIGHT - 8; if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); + CG_DrawPic( x - POS_FLAG_X, y1-POS_FLAG_Y, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); } } } @@ -922,11 +1115,11 @@ static float CG_DrawScores( float y ) { s = va( "%2i", s1 ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_FillRect( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, color ); if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + CG_DrawPic( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } - CG_DrawBigString( x + 4, y, s, 1.0F); + CG_DrawBigString( x + 4, y - POS_STR_Y, s, 1.0F); if ( cgs.gametype == GT_CTF ) { // Display flag status @@ -935,7 +1128,7 @@ static float CG_DrawScores( float y ) { if (item) { y1 = y - BIGCHAR_HEIGHT - 8; if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); + CG_DrawPic( x - POS_FLAG_X, y1-POS_FLAG_Y, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); } } } @@ -949,13 +1142,13 @@ static float CG_DrawScores( float y ) { s = va( "%2i", v ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; - CG_DrawBigString( x + 4, y, s, 1.0F); + CG_DrawBigString( x + 4, y - POS_STR_Y, s, 1.0F); } } else { qboolean spectator; - x = 640; + x = 630; // BFP - Before Q3: 640 score = cg.snap->ps.persistant[PERS_SCORE]; spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ); @@ -972,16 +1165,16 @@ static float CG_DrawScores( float y ) { color[1] = 0.0f; color[2] = 0.0f; color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + CG_FillRect( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } else { color[0] = 0.5f; color[1] = 0.5f; color[2] = 0.5f; color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_FillRect( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, color ); } - CG_DrawBigString( x + 4, y, s, 1.0F); + CG_DrawBigString( x + 4, y - POS_STR_Y, s, 1.0F); } // first place @@ -994,23 +1187,23 @@ static float CG_DrawScores( float y ) { color[1] = 0.0f; color[2] = 1.0f; color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + CG_FillRect( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } else { color[0] = 0.5f; color[1] = 0.5f; color[2] = 0.5f; color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_FillRect( x, y-POS_STR_Y, w, BIGCHAR_HEIGHT+8, color ); } - CG_DrawBigString( x + 4, y, s, 1.0F); + CG_DrawBigString( x + 4, y - POS_STR_Y, s, 1.0F); } if ( cgs.fraglimit ) { s = va( "%2i", cgs.fraglimit ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; - CG_DrawBigString( x + 4, y, s, 1.0F); + CG_DrawBigString( x + 4, y - POS_STR_Y, s, 1.0F); } } @@ -1018,6 +1211,8 @@ static float CG_DrawScores( float y ) { return y1 - 8; } +// BFP - Don't draw powerups +#if 0 /* ================ CG_DrawPowerups @@ -1116,6 +1311,7 @@ static float CG_DrawPowerups( float y ) { return y; } +#endif /* ===================== @@ -1128,12 +1324,23 @@ static void CG_DrawLowerRight( void ) { y = 480 - ICON_SIZE; + // BFP - CG_DrawTeamOverlay and CG_DrawScores are placed to get better visibility with cg_drawTeamOverlay 2 + + if ( cg.snap->ps.stats[STAT_HEALTH] > 0 ) { // BFP - Don't draw if the player is dead + // before Q3: y = CG_DrawScores( y ); + CG_DrawScores( y ); + } + + if ( cg.snap->ps.stats[STAT_HEALTH] > 0 && cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { // BFP - Don't draw if the player is dead or spectating + // BFP - HUD overlay + CG_DrawHUDOverlay(); + } + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { y = CG_DrawTeamOverlay( y, qtrue, qfalse ); } - - y = CG_DrawScores( y ); - y = CG_DrawPowerups( y ); + // BFP - Don't draw powerups + // y = CG_DrawPowerups( y ); } /* @@ -1166,6 +1373,27 @@ static int CG_DrawPickupItem( int y ) { return y; } +/* +===================== +CG_DrawKiAttackChargeUpPoints +===================== +*/ +static void CG_DrawKiAttackChargeUpPoints( void ) { // BFP - Ki attack charge up points + int x = 105, i = 1; + + if ( cg.predictedPlayerState.generic1 > 0 + // don't draw unless the player is charging/exploding a ki wave + && ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING || cg.predictedPlayerState.weaponstate == WEAPON_ACTIVE ) ) { + while ( i <= 6 ) { + if ( cg.predictedPlayerState.generic1 >= i ) { + CG_DrawPic( x, 412, BIGCHAR_WIDTH - 2, BIGCHAR_HEIGHT - 2, cgs.media.chargeupbuttgreen ); + } + x += 16; + ++i; + } + } +} + /* ===================== CG_DrawLowerLeft @@ -1177,11 +1405,23 @@ static void CG_DrawLowerLeft( void ) { y = 480 - ICON_SIZE; + if ( cg.snap->ps.stats[STAT_HEALTH] > 0 && cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { // BFP - Don't draw if the player is dead or spectating + // BFP - Selected ki attack + CG_DrawSelectedKiAttack(); + + // BFP - Place head in the corner + CG_DrawStatusBarHead( 0 ); // CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); + + // BFP - Ki attack charge up points + CG_DrawKiAttackChargeUpPoints(); + } + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { y = CG_DrawTeamOverlay( y, qfalse, qfalse ); } - + // BFP - Put the pickup item icon a bit up + y = 480 - ICON_SIZE - 32; y = CG_DrawPickupItem( y ); } @@ -1356,7 +1596,7 @@ LAGOMETER =============================================================================== */ -#define LAG_SAMPLES 128 +#define LAG_SAMPLES 256 typedef struct { @@ -1459,7 +1699,7 @@ static void CG_DrawLagometer( void ) { int a, x, y, i; float v; float ax, ay, aw, ah, mid, range; - int color; + int color, d; float vscale; if ( !cg_lagometer.integer || cgs.localServer ) { @@ -1471,7 +1711,7 @@ static void CG_DrawLagometer( void ) { // draw the graph // x = 640 - 48; - y = 480 - 48; + y = 480 - 200; // BFP - Put the lagometer at the center/a bit up. Before Q3: 480 - 48 trap_R_SetColor( NULL ); CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); @@ -1488,6 +1728,8 @@ static void CG_DrawLagometer( void ) { vscale = range / MAX_LAGOMETER_RANGE; + d = (int)(ax + aw); + // draw the frame interpoalte / extrapolate graph for ( a = 0 ; a < aw ; a++ ) { i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); @@ -1501,7 +1743,7 @@ static void CG_DrawLagometer( void ) { if ( v > range ) { v = range; } - trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic ( d - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } else if ( v < 0 ) { if ( color != 2 ) { color = 2; @@ -1511,7 +1753,7 @@ static void CG_DrawLagometer( void ) { if ( v > range ) { v = range; } - trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( d - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } } @@ -1538,13 +1780,13 @@ static void CG_DrawLagometer( void ) { if ( v > range ) { v = range; } - trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( d - 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_DrawStretchPic( d - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); } } @@ -1557,6 +1799,30 @@ static void CG_DrawLagometer( void ) { CG_DrawDisconnect(); } +/* +================== +CG_DrawNumAlivePlayers +================== +*/ +static void CG_DrawNumAlivePlayers( void ) { // BFP - Show number of alive players for the player itself + // if the player is the only one playing, the counter won't be shown + int i = 0, alivePlayers = 0; + while ( i < MAX_CLIENTS ) { + if ( cg_entities[i].currentValid && !( cg_entities[i].currentState.eFlags & EF_DEAD ) ) { + if ( cgs.gametype >= GT_TEAM + && cg.snap->ps.persistant[PERS_TEAM] == cgs.clientinfo[i].team ) { + ++alivePlayers; + } else { + ++alivePlayers; + } + } + ++i; + } + // don't show number of alive players when spectating and when there's only one client + if ( alivePlayers > 0 && cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + CG_DrawBigString( 608, 255, va( "%d", alivePlayers ), 1.0F ); + } +} /* @@ -1711,6 +1977,15 @@ static void CG_DrawCrosshair(void) { #endif static float lastPositionY = 480.0f; // BFP - Last Y position for traceable crosshair to move smoothly like BFP vanilla does + // BFP - Frametime handling + static int previous; + int t, frameTime; + + // BFP - Handle frametime to avoid being timescaled + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + // BFP - TODO: BFP doesn't use the crosshair as player view, // e.g. if the camera angle is 90º, the crosshair should look what's in this view, // not what the player sees @@ -1753,9 +2028,6 @@ static void CG_DrawCrosshair(void) { // BFP - Make crosshair size starting from biggest to current w = LERP( w*2, w, f ); // before: w *= ( 1 + f ); h = LERP( h*2, h, f ); // before: h *= ( 1 + f ); - if ( cg_crosshairHealth.integer && f <= 0.35f ) { // BFP - BFP crosshair health feature - trap_R_SetColor( colorRed ); - } } x = cg_crosshairX.integer; @@ -1766,6 +2038,9 @@ static void CG_DrawCrosshair(void) { ca = 0; } hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; + if ( cg_crosshairHealth.integer && f <= 0.425f ) { // BFP - BFP crosshair health feature + hShader = cgs.media.redCrosshairShader[ ca % NUM_CROSSHAIRS ]; + } if ( cg_thirdPerson.integer >= 1 && cg_stableCrosshair.integer <= 0 ) { // BFP - Third person traceable crosshair #if ESF_STYLE @@ -1788,12 +2063,12 @@ static void CG_DrawCrosshair(void) { CG_AdjustFrom640( &x, &y, &w, &h ); // BFP - Make the traceable crosshair move smoothly like BFP vanilla does - // LERP( , , (float)(cg.frametime / 1000.00f) * ); + // LERP( , , (float)(frameTime / 1000.00f) * ); #if ESF_STYLE - x = LERP( lastPositionX, x, (float)(cg.frametime / 1000.00f) * 18.0f ); - y = LERP( lastPositionY, y, (float)(cg.frametime / 1000.00f) * 18.0f ); + x = LERP( lastPositionX, x, (float)(frameTime / 1000.00f) * 18.0f ); + y = LERP( lastPositionY, y, (float)(frameTime / 1000.00f) * 18.0f ); #else - y = LERP( lastPositionY, y, (float)(cg.frametime / 1000.00f) * 12.0f ); + y = LERP( lastPositionY, y, (float)(frameTime / 1000.00f) * 12.0f ); #endif trap_R_DrawStretchPic( x - 0.5f * w, // 492.799987 @@ -1840,10 +2115,13 @@ static void CG_ScanForCrosshairEntity( void ) { return; } + // BFP - No invis powerup +#if 0 // if the player is invisible, don't show it if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { return; } +#endif // update the fade timer cg.crosshairClientNum = trace.entityNum; @@ -1858,8 +2136,10 @@ CG_DrawCrosshairNames */ static void CG_DrawCrosshairNames( void ) { float *color; - char *name; + // BFP - Show powerlevel from the other player + char *namePowerlevel; float w; + int powerlevel = -1; if ( !cg_drawCrosshair.integer ) { return; @@ -1884,9 +2164,22 @@ static void CG_DrawCrosshairNames( void ) { return; } - name = cgs.clientinfo[ cg.crosshairClientNum ].name; - w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; - CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f ); + // BFP - Draw name and powerlevel from the other player + namePowerlevel = cgs.clientinfo[ cg.crosshairClientNum ].name; + powerlevel = cg_entities[ cg.crosshairClientNum ].currentState.frame; + w = CG_DrawStrlen( namePowerlevel ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w / 2, 170, namePowerlevel, color[3] * 0.5f ); + trap_R_SetColor( NULL ); + + // BFP - NOTE: Just curious, BFP had a 16-bit size issue (only reaches 32767 - the maximum), + // they thought that adding "000" at the last can be entertaining for the player powerlevel + namePowerlevel = va( "%i000", powerlevel ); + // BFP - If the powerlevel is higher than 1000, set to this limit + if ( powerlevel >= 1000 ) { + namePowerlevel = "1 Mil"; + } + w = CG_DrawStrlen( namePowerlevel ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w / 2, 190, namePowerlevel, color[3] * 0.5f ); trap_R_SetColor( NULL ); } @@ -1900,11 +2193,12 @@ CG_DrawSpectator */ static void CG_DrawSpectator(void) { CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F); - if ( cgs.gametype == GT_TOURNAMENT ) { + if ( cgs.gametype == GT_TOURNAMENT + || cgs.gametype == GT_SURVIVAL ) { // BFP - Survival CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F); } else if ( cgs.gametype >= GT_TEAM ) { - CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F); + CG_DrawBigString(320 - 25 * 8, 460, "use the TEAM menu to play", 1.0F); } } @@ -1971,7 +2265,7 @@ static void CG_DrawTeamVote(void) { } -static qboolean CG_DrawScoreboard() { +static qboolean CG_DrawScoreboard( void ) { return CG_DrawOldScoreboard(); } @@ -2029,7 +2323,7 @@ CG_DrawKiWarning */ static void CG_DrawKiWarning( void ) { const char *s; - int w; + int w, y = 64; if ( cg_drawKiWarning.integer <= 0 ) { // BFP - ki warning (before cg_drawAmmoWarning) return; @@ -2055,8 +2349,48 @@ static void CG_DrawKiWarning( void ) { } else if ( cg.predictedPlayerState.stats[STAT_KI] < 0 ) { s = "OUT OF KI"; } + + // BFP - Put the center notification a bit lower when spectating and following a player + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + y *= 2; + } w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString(320 - w / 2, 64, s, 1.0F); + CG_DrawBigString(320 - w / 2, y, s, 1.0F); +} + +/* +================ +CG_DrawBlindEffect + +Draws a white screen overlay that fades out (Blinding Flash effect) +================ +*/ +static void CG_DrawBlindEffect( void ) { // BFP - Blind + const float BLIND_ALPHA_TIME = 17000.0f, + BLIND_DURATION = 6000.0f; // original BFP lasts 6 seconds with that opacity + float blindAlpha = 0.0f; + float elapsed = (float)( cg.time - cg.blindStartTime ); + + if ( !cg.blind ) { + return; + } + + if ( elapsed >= BLIND_ALPHA_TIME ) { + cg.blind = qfalse; + return; + } + + if ( elapsed < BLIND_DURATION ) { + blindAlpha = 1.0f - ( elapsed / BLIND_ALPHA_TIME ); + } + + if ( blindAlpha > 0.0f ) { + vec4_t color = { 1.0f, 1.0f, 1.0f, 1.0f }; + color[3] = blindAlpha; + trap_R_SetColor( color ); + CG_DrawPic( 0, 0, 640, 480, cgs.media.whiteShader ); + trap_R_SetColor( NULL ); + } } /* @@ -2066,24 +2400,46 @@ CG_DrawHitStun */ static void CG_DrawHitStun( void ) { // BFP - Hit stun bottom centerprint const char *s; - int w; - playerState_t *ps; - int value; + int w, t; s = ""; // avoid printing when there are no status changes, for dll and shared objects - if ( cg.predictedPlayerState.stats[STAT_KI] <= 0 ) { + // 900 is added to adjust the timer calculated in milliseconds + t = ( 900 + cg.predictedPlayerState.stats[STAT_HITSTUN_TIME] ) / 1000; + if ( cg.predictedPlayerState.stats[STAT_HITSTUN_TIME] > 0 ) { s = "Stun"; } w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - value = 0; // BFP - TODO: hit stun time - UI_DrawProportionalString( 320 - w / 2, SCREEN_HEIGHT - ( BIGCHAR_HEIGHT * 6 ), + UI_DrawProportionalString( 320 - w / 2, SCREEN_HEIGHT - ( BIGCHAR_HEIGHT * 6 ) + 24, s, UI_SMALLFONT, colorWhite ); - // BFP - TODO: Draw the time (seconds) - if ( value > 0 ) { - CG_DrawField ( 320 - w / 2, SCREEN_HEIGHT - ( BIGCHAR_HEIGHT * 4 ), 3, value ); + if ( cg.predictedPlayerState.stats[STAT_HITSTUN_TIME] > 0 ) { + if ( t <= 0 ) { + t = 1; + } + CG_DrawField ( 320 - w - 16, SCREEN_HEIGHT - ( BIGCHAR_HEIGHT * 4 ) + 12, 3, t ); } } +/* +================= +CG_DrawReadyKiAttack +================= +*/ +static void CG_DrawReadyKiAttack( void ) { // BFP - Ready message in the bottom centerprint when charging attacks + const char *s; + int w; + + s = ""; // avoid printing when there are no status changes, for dll and shared objects + if ( cg_chargeupAlert.integer <= 0 ) { // if disabled, don't show it + return; + } + if ( cg.predictedPlayerState.eFlags & EF_READY_KI_ATTACK ) { + s = "Ready"; + } + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + UI_DrawProportionalString( 327 - w / 2, SCREEN_HEIGHT - ( BIGCHAR_HEIGHT * 6 ) + 76, + s, UI_SMALLFONT, colorWhite ); +} + /* ================= CG_DrawWarmup @@ -2093,7 +2449,8 @@ static void CG_DrawWarmup( void ) { int w; int sec; int i; - float scale; + // BFP - Unused variable, wth? + // float scale; clientInfo_t *ci1, *ci2; int cw; const char *s; @@ -2111,7 +2468,8 @@ static void CG_DrawWarmup( void ) { return; } - if (cgs.gametype == GT_TOURNAMENT) { + if ( cgs.gametype == GT_TOURNAMENT + || cgs.gametype == GT_SURVIVAL ) { // BFP - Survival) { // find the two active players ci1 = NULL; ci2 = NULL; @@ -2178,23 +2536,25 @@ static void CG_DrawWarmup( void ) { break; } } - scale = 0.45f; + + // BFP - Unused scale variable on this logic, wth? + // scale = 0.45f; switch ( cg.warmupCount ) { case 0: cw = 28; - scale = 0.54f; + // scale = 0.54f; break; case 1: cw = 24; - scale = 0.51f; + // scale = 0.51f; break; case 2: cw = 20; - scale = 0.48f; + // scale = 0.48f; break; default: cw = 16; - scale = 0.45f; + // scale = 0.45f; break; } @@ -2223,6 +2583,9 @@ static void CG_Draw2D( void ) { return; } + // BFP - Draw blind effect (Blinding Flash) + CG_DrawBlindEffect(); + /* if (cg.cameraMode) { return; @@ -2233,11 +2596,13 @@ static void CG_Draw2D( void ) { CG_DrawCrosshair(); CG_DrawCrosshairNames(); } else { - // don't draw any status if dead or the scoreboard is being explicitly shown - if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { + // BFP - Q3 before: don't draw any status if dead or the scoreboard is being explicitly shown + if ( cg.snap->ps.stats[STAT_HEALTH] > 0 ) { CG_DrawStatusBar(); CG_DrawKiWarning(); // BFP - ki warning CG_DrawHitStun(); // BFP - Hit stun bottom centerprint + CG_DrawReadyKiAttack(); // BFP - Ready message in the bottom centerprint when charging attacks + CG_DrawNumAlivePlayers(); // BFP - Number of alive players CG_DrawCrosshair(); CG_DrawCrosshairNames(); CG_DrawWeaponSelect(); @@ -2270,10 +2635,12 @@ static void CG_Draw2D( void ) { } } - -static void CG_DrawTourneyScoreboard() { +// BFP - Unused function +#if 0 +static void CG_DrawTourneyScoreboard( void ) { CG_DrawOldTourneyScoreboard(); } +#endif /* ===================== diff --git a/source/cgame/cg_drawtools.c b/source/cgame/cg_drawtools.c index 2ec45ed..f7ff3bc 100644 --- a/source/cgame/cg_drawtools.c +++ b/source/cgame/cg_drawtools.c @@ -30,20 +30,16 @@ 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 +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) +{ // scale for screen sizes - *x *= cgs.screenXScale; - *y *= cgs.screenYScale; + *x = *x * cgs.screenXScale + cgs.screenXBias; + *y = *y * cgs.screenYScale + cgs.screenYBias; *w *= cgs.screenXScale; *h *= cgs.screenYScale; } + /* ================ CG_FillRect @@ -60,6 +56,20 @@ void CG_FillRect( float x, float y, float width, float height, const float *colo trap_R_SetColor( NULL ); } + +/* +================ +CG_FillScreen +================ +*/ +void CG_FillScreen( const float *color ) +{ + trap_R_SetColor( color ); + trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_SetColor( NULL ); +} + + /* ================ CG_DrawSides @@ -74,12 +84,15 @@ void CG_DrawSides(float x, float y, float w, float h, float size) { 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 @@ -90,14 +103,13 @@ Coordinates are 640*480 virtual values void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { trap_R_SetColor( color ); - CG_DrawTopBottom(x, y, width, height, size); - CG_DrawSides(x, y, width, height, size); + CG_DrawTopBottom(x, y, width, height, size); + CG_DrawSides(x, y, width, height, size); trap_R_SetColor( NULL ); } - /* ================ CG_DrawPic @@ -111,7 +123,6 @@ void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader } - /* =============== CG_DrawChar @@ -119,7 +130,7 @@ CG_DrawChar Coordinates and size in 640*480 virtual screen size =============== */ -void CG_DrawChar( int x, int y, int width, int height, int ch ) { +static void CG_DrawChar( int x, int y, int width, int height, int ch ) { int row, col; float frow, fcol; float size; @@ -238,6 +249,520 @@ 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 ); } +// new font renderer + +#ifdef USE_NEW_FONT_RENDERER + +#define MAX_FONT_SHADERS 4 + +typedef struct { + float tc_prop[4]; + float tc_mono[4]; + float space1; + float space2; + float width; +} font_metric_t; + +typedef struct { + font_metric_t metrics[256]; + qhandle_t shader[ MAX_FONT_SHADERS ]; + int shaderThreshold[ MAX_FONT_SHADERS ]; + int shaderCount; +} font_t; + +static font_t bigchars; +static font_t numbers; +static const font_t *font = &bigchars; +static const font_metric_t *metrics = &bigchars.metrics[0]; + + +void CG_SelectFont( int index ) +{ + if ( index == 0 ) + font = &bigchars; + else + font = &numbers; + + metrics = &font->metrics[0]; +} + + +static qboolean CG_FileExist( const char *file ) +{ + fileHandle_t f; + + if ( !file || !file[0] ) + return qfalse; + + trap_FS_FOpenFile( file, &f, FS_READ ); + if ( f == FS_INVALID_HANDLE ) + return qfalse; + else { + trap_FS_FCloseFile( f ); + return qtrue; + } +} + + +static void CG_LoadFont( font_t *fnt, const char *fontName ) +{ + char buf[ 8000 ]; + fileHandle_t f; + char *token, *text; + float width, height, r_width, r_height; + float char_width; + float char_height; + char shaderName[ MAX_FONT_SHADERS ][ MAX_QPATH ], tmpName[ MAX_QPATH ]; + int shaderCount; + int shaderThreshold[ MAX_FONT_SHADERS ]; + font_metric_t *fm; + int i, tmp, len, chars; + float w1, w2; + float s1, s2; + float x0, y0; + qboolean swapped; + + memset( fnt, 0, sizeof( *fnt ) ); + + len = trap_FS_FOpenFile( fontName, &f, FS_READ ); + if ( f == FS_INVALID_HANDLE ) { + CG_Printf( S_COLOR_YELLOW "CG_LoadFont: error opening %s\n", fontName ); + return; + } + + if ( len >= sizeof( buf ) ) { + CG_Printf( S_COLOR_YELLOW "CG_LoadFont: font file is too long: %i\n", len ); + len = sizeof( buf )-1; + } + + trap_FS_Read( buf, len, f ); + trap_FS_FCloseFile( f ); + buf[ len ] = '\0'; + + shaderCount = 0; + + text = buf; // initialize parser + COM_BeginParseSession( fontName ); + + while ( 1 ) + { + token = COM_ParseExt( &text, qtrue ); + if ( token[0] == '\0' ) { + Com_Printf( S_COLOR_RED "CG_LoadFont: parse error.\n" ); + return; + } + + // font image + if ( strcmp( token, "img" ) == 0 ) { + if ( shaderCount >= MAX_FONT_SHADERS ) { + Com_Printf( "CG_LoadFont: too many font images, ignoring.\n" ); + SkipRestOfLine( &text ); + continue; + } + token = COM_ParseExt( &text, qfalse ); + if ( !CG_FileExist( token ) ) { + Com_Printf( "CG_LoadFont: font image '%s' doesn't exist.\n", token ); + return; + } + // save shader name + Q_strncpyz( shaderName[ shaderCount ], token, sizeof( shaderName[ shaderCount ] ) ); + // get threshold + token = COM_ParseExt( &text, qfalse ); + shaderThreshold[ shaderCount ] = atoi( token ); + + //Com_Printf( S_COLOR_CYAN "img: %s, threshold: %i\n", shaderName[ shaderCount ], shaderThreshold[ shaderCount ] ); + shaderCount++; + + SkipRestOfLine( &text ); + continue; + } + + // font parameters + if ( strcmp( token, "fnt" ) == 0 ) { + token = COM_ParseExt( &text, qfalse ); + if ( token[0] == '\0' || (width = atof( token )) <= 0.0 ) { + Com_Printf( "CG_LoadFont: error reading image width.\n" ); + return; + } + r_width = 1.0 / width; + + token = COM_ParseExt( &text, qfalse ); + if ( token[0] == '\0' || (height = atof( token )) <= 0.0 ) { + Com_Printf( "CG_LoadFont: error reading image height.\n" ); + return; + } + r_height = 1.0 / height; + + token = COM_ParseExt( &text, qfalse ); + if ( token[0] == '\0' ) { + Com_Printf( "CG_LoadFont: error reading char widht.\n" ); + return; + } + char_width = atof( token ); + + token = COM_ParseExt( &text, qfalse ); + if ( token[0] == '\0' ) { + Com_Printf( "CG_LoadFont: error reading char height.\n" ); + return; + } + char_height = atof( token ); + + break; // parse char metrics + } + } + + if ( shaderCount == 0 ) { + Com_Printf( "CG_LoadFont: no font images specified in %s.\n", fontName ); + return; + } + + fm = fnt->metrics; + + chars = 0; + for ( ;; ) { + // char index + token = COM_ParseExt( &text, qtrue ); + if ( !token[0] ) + break; + + if ( token[0] == '\'' && token[1] && token[2] == '\'' ) // char code in form 'X' + i = token[1] & 255; + else // integer code + i = atoi( token ); + + if ( i < 0 || i > 255 ) { + CG_Printf( S_COLOR_RED "CG_LoadFont: bad char index %i.\n", i ); + return; + } + fm = fnt->metrics + i; + + // x0 + token = COM_ParseExt( &text, qfalse ); + if ( !token[0] ) { + CG_Printf( S_COLOR_RED "CG_LoadFont: error reading x0.\n" ); + return; + } + x0 = atof( token ); + + // y0 + token = COM_ParseExt( &text, qfalse ); + if ( !token[0] ) { + CG_Printf( S_COLOR_RED "CG_LoadFont: error reading y0.\n" ); + return; + } + y0 = atof( token ); + + // w1-offset + token = COM_ParseExt( &text, qfalse ); + if ( !token[0] ) { + CG_Printf( S_COLOR_RED "CG_LoadFont: error reading x-offset.\n" ); + return; + } + w1 = atof( token ); + + // w2-offset + token = COM_ParseExt( &text, qfalse ); + if ( !token[0] ) { + CG_Printf( S_COLOR_RED "CG_LoadFont: error reading x-length.\n" ); + return; + } + w2 = atof( token ); + + // space1 + token = COM_ParseExt( &text, qfalse ); + if ( !token[0] ) { + CG_Printf( S_COLOR_RED "CG_LoadFont: error reading space1.\n" ); + return; + } + s1 = atof( token ); + + // space2 + token = COM_ParseExt( &text, qfalse ); + if ( !token[0] ) { + CG_Printf( S_COLOR_RED "CG_LoadFont: error reading space2.\n" ); + return; + } + s2 = atof( token ); + + fm->tc_mono[0] = x0 * r_width; + fm->tc_mono[1] = y0 * r_height; + fm->tc_mono[2] = ( x0 + char_width ) * r_width; + fm->tc_mono[3] = ( y0 + char_height ) * r_height; + + // proportional y-coords is matching with mono + fm->tc_prop[1] = fm->tc_mono[1]; + fm->tc_prop[3] = fm->tc_mono[3]; + + fm->width = w2 / char_width; + fm->space1 = s1 / char_width; + fm->space2 = (s2 + w2) / char_width; + fm->tc_prop[0] = fm->tc_mono[0] + (w1 * r_width); + fm->tc_prop[2] = fm->tc_prop[0] + (w2 * r_width); + + chars++; + + SkipRestOfLine( &text ); + } + + // sort images by threshold + do { + for ( swapped = qfalse, i = 1 ; i < shaderCount; i++ ) { + if ( shaderThreshold[i-1] > shaderThreshold[i] ) { + tmp = shaderThreshold[i-1]; + shaderThreshold[i-1] = shaderThreshold[i]; + shaderThreshold[i] = tmp; + strcpy( tmpName, shaderName[i-1] ); + strcpy( shaderName[i-1], shaderName[i] ); + strcpy( shaderName[i], tmpName ); + swapped = qtrue; + } + } + } while ( swapped ); + + // always assume zero threshold for lowest-quality shader + shaderThreshold[0] = 0; + + fnt->shaderCount = shaderCount; + for ( i = 0; i < shaderCount; i++ ) { + fnt->shader[i] = trap_R_RegisterShaderNoMip( shaderName[i] ); + fnt->shaderThreshold[i] = shaderThreshold[i]; + } + + CG_Printf( "Font '%s' loaded with %i chars and %i images\n", fontName, chars, shaderCount ); +} + + +void CG_LoadFonts( void ) +{ + CG_LoadFont( &bigchars, "gfx/2d/bigchars.cfg" ); + CG_LoadFont( &numbers, "gfx/2d/numbers.cfg" ); +} + + +static float DrawStringLength( const char *string, float ax, float aw, float max_ax, int proportional ) +{ + const font_metric_t *fm; + //float aw1; + float x_end; + const byte *s; + float xx; + + if ( !string ) + return 0.0f; + + s = (const byte*)string; + + xx = ax; + + while ( *s != '\0' ) { + + if ( *s == Q_COLOR_ESCAPE && s[1] != '\0' && s[1] != '^' ) { + //if ( !(flags & DS_SHOW_CODE) ) { + s += 2; + continue; + //} + } + + //fm = &font->metrics[ *s ]; + fm = &metrics[ *s ]; + if ( proportional ) { + //aw1 = fm->width * aw; + ax += fm->space1 * aw; // add extra space if required by metrics + x_end = ax + fm->space2 * aw; // final position + } else { + //aw1 = aw; + x_end = ax + aw; + } + + if ( x_end > max_ax ) + break; + + ax = x_end; + s++; + } + + return (ax - xx); +} + + +void CG_DrawString( float x, float y, const char *string, const vec4_t setColor, float charWidth, float charHeight, int maxChars, int flags ) +{ + const font_metric_t *fm; + const float *tc; // texture coordinates for char + float ax, ay, aw, aw1, ah; // absolute positions/dimensions + float scale; + float x_end, xx; + vec4_t color; + const byte *s; + float xx_add, yy_add; + float max_ax; + int i; + qhandle_t sh; + int proportional; + + if ( !string ) + return; + + s = (const byte *)string; + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenYScale + cgs.screenYBias; + + aw = charWidth * cgs.screenXScale; + ah = charHeight * cgs.screenYScale; + + if ( maxChars <= 0 ) { + max_ax = 9999999.0f; + } else { + max_ax = ax + aw * maxChars; + } + + proportional = (flags & DS_PROPORTIONAL); + + if ( flags & ( DS_CENTER | DS_RIGHT ) ) { + if ( flags & DS_CENTER ) { + ax -= 0.5f * DrawStringLength( string, ax, aw, max_ax, proportional ); + } else { + ax -= DrawStringLength( string, ax, aw, max_ax, proportional ); + } + } + + sh = font->shader[0]; // low-res shader by default + + if ( flags & DS_SHADOW ) { + xx = ax; + + // calculate shadow offsets + scale = charWidth * 0.075f; // charWidth/15 + xx_add = scale * cgs.screenXScale; + yy_add = scale * cgs.screenYScale; + + color[0] = color[1] = color[2] = 0.0f; + color[3] = setColor[3] * 0.5f; + trap_R_SetColor( color ); + + while ( *s != '\0' ) { + if ( *s == Q_COLOR_ESCAPE && s[1] != '\0' && s[1] != '^' ) { + //if ( !(options & DS_SHOW_CODE) ) { + s += 2; + continue; + //} + } + //fm = &font->metrics[ *s ]; + fm = &metrics[ *s ]; + if ( proportional ) { + tc = fm->tc_prop; + aw1 = fm->width * aw; + ax += fm->space1 * aw; // add extra space if required by metrics + x_end = ax + fm->space2 * aw; // final position + } else { + tc = fm->tc_mono; + aw1 = aw; + x_end = ax + aw; + } + + if ( x_end > max_ax || ax >= cgs.glconfig.vidWidth ) + break; + + trap_R_DrawStretchPic( ax + xx_add, ay + yy_add, aw1, ah, tc[0], tc[1], tc[2], tc[3], sh ); + + ax = x_end; + s++; + } + + // recover altered parameters + s = (const byte*)string; + ax = xx; + } + + // select hi-res shader if accepted + for ( i = 1; i < font->shaderCount; i++ ) { + if ( ah >= font->shaderThreshold[i] ) { + sh = font->shader[i]; + } + } + + Vector4Copy( setColor, color ); + trap_R_SetColor( color ); + + while ( *s != '\0' ) { + + if ( *s == Q_COLOR_ESCAPE && s[1] != '\0' && s[1] != '^' ) { + if ( !( flags & DS_FORCE_COLOR ) ) { + VectorCopy( g_color_table[ ColorIndex( s[1] ) ], color ); + trap_R_SetColor( color ); + } + //if ( !(options & DS_SHOW_CODE) ) { + s += 2; + continue; + //} + } + + //fm = &font->metrics[ *s ]; + fm = &metrics[ *s ]; + if ( proportional ) { + tc = fm->tc_prop; + aw1 = fm->width * aw; + ax += fm->space1 * aw; // add extra space if required by metrics + x_end = ax + fm->space2 * aw; // final position + } else { + tc = fm->tc_mono; + aw1 = aw; + x_end = ax + aw; + } + + if ( x_end > max_ax || ax >= cgs.glconfig.vidWidth ) + break; + + trap_R_DrawStretchPic( ax, ay, aw1, ah, tc[0], tc[1], tc[2], tc[3], sh ); + + ax = x_end; + s++; + } + + //trap_R_SetColor( NULL ); +} +#else + + +static float DrawStringLen( const char *s, float charWidth ) +{ + int count; + count = 0; + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + return count * charWidth; +} + + +void CG_DrawString( float x, float y, const char *s, const vec4_t color, float charWidth, float charHeight, int maxChars, int flags ) +{ + if ( !color ) + { + color = g_color_table[ ColorIndex( COLOR_WHITE ) ]; + } + + if ( flags & ( DS_CENTER | DS_RIGHT ) ) + { + float w; + w = DrawStringLen( s, charWidth ); + if ( flags & DS_CENTER ) + x -= w * 0.5f; + else + x -= w; + } + + CG_DrawStringExt( x, y, s, color, flags & DS_FORCE_COLOR, flags & DS_SHADOW, charWidth, charHeight, maxChars ); +} +#endif + + /* ================= CG_DrawStrlen @@ -261,6 +786,7 @@ int CG_DrawStrlen( const char *str ) { return count; } + /* ============= CG_TileClearBox @@ -280,7 +806,6 @@ static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { } - /* ============== CG_TileClear @@ -319,7 +844,6 @@ void CG_TileClear( void ) { } - /* ================ CG_FadeColor @@ -351,12 +875,43 @@ float *CG_FadeColor( int startMsec, int totalMsec ) { } +/* +================ +CG_FadeColorTime +================ +*/ +float *CG_FadeColorTime( int startMsec, int totalMsec, int fadeMsec ) { + 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 < fadeMsec ) { + color[3] = ( totalMsec - t ) * 1.0f/(float)fadeMsec; + } else { + color[3] = 1.0f; + } + color[0] = color[1] = color[2] = 1.0f; + + return color; +} + + /* ================ CG_TeamColor ================ */ -float *CG_TeamColor( int team ) { +const float *CG_TeamColor( team_t team ) { static vec4_t red = {1, 0.2f, 0.2f, 1}; static vec4_t blue = {0.2f, 0.2f, 1, 1}; static vec4_t other = {1, 1, 1, 1}; @@ -419,6 +974,7 @@ void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { } } + /* ================= CG_ColorForHealth @@ -432,7 +988,6 @@ void CG_ColorForHealth( vec4_t hcolor ) { - // bk001205 - code below duplicated in q3_ui/ui-atoms.c // bk001205 - FIXME: does this belong in ui_shared.c? // bk001205 - FIXME: HARD_LINKED flags not visible here @@ -610,7 +1165,7 @@ static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) trap_R_SetColor( color ); ax = x * cgs.screenXScale + cgs.screenXBias; - ay = y * cgs.screenXScale; + ay = y * cgs.screenYScale + cgs.screenYBias; s = str; while ( *s ) @@ -720,7 +1275,7 @@ static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t co trap_R_SetColor( color ); ax = x * cgs.screenXScale + cgs.screenXBias; - ay = y * cgs.screenXScale; + ay = y * cgs.screenYScale + cgs.screenYBias; s = str; while ( *s ) @@ -814,7 +1369,7 @@ void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t drawcolor[0] = color[0]; drawcolor[1] = color[1]; drawcolor[2] = color[2]; - drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); + drawcolor[3] = 0.5 + 0.5 * sin( ( cg.time % TMOD_075 ) / PULSE_DIVISOR ); UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); return; } diff --git a/source/cgame/cg_effects.c b/source/cgame/cg_effects.c index 73c6656..02983d9 100644 --- a/source/cgame/cg_effects.c +++ b/source/cgame/cg_effects.c @@ -162,7 +162,7 @@ CG_SpawnEffect Player teleporting in or out ================== */ -void CG_SpawnEffect( vec3_t org ) { +refEntity_t *CG_SpawnEffect( const vec3_t origin, qboolean firstPerson ) { localEntity_t *le; refEntity_t *re; @@ -184,8 +184,19 @@ void CG_SpawnEffect( vec3_t org ) { re->hModel = cgs.media.teleportEffectModel; AxisClear( re->axis ); - VectorCopy( org, re->origin ); - re->origin[2] -= 24; + VectorCopy( origin, re->origin ); + if ( firstPerson ) { + // adjust teleport effect model to cover whole FOV for better immersion + re->origin[2] -= 5.0f; + re->renderfx = RF_FIRST_PERSON; + // add second model visible through portals only + re = CG_SpawnEffect( origin, qfalse ); + re->renderfx = RF_THIRD_PERSON; + } else { + re->origin[2] -= 24.0f; + } + + return re; } /* @@ -294,6 +305,51 @@ localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, return ex; } +/* +==================== +CG_SpawnExplosionModel +==================== +*/ +localEntity_t *CG_SpawnExplosionModel( vec3_t origin, vec3_t dir, leType_t type, qhandle_t hModel, qhandle_t shader, float duration ) { // BFP - Explosion sphere, shell and ring effect + localEntity_t *le; + vec3_t newOrigin; + + le = CG_AllocLocalEntity(); + + le->leType = type; + + le->refEntity.hModel = hModel; + le->refEntity.customShader = shader; + + VectorCopy( origin, newOrigin ); + + if ( !dir ) { + AxisClear( le->refEntity.axis ); + } else { + VectorCopy( dir, le->refEntity.axis[0] ); + RotateAroundDirection( le->refEntity.axis, 0 ); + } + + VectorCopy( origin, le->refEntity.origin ); + VectorCopy( newOrigin, le->refEntity.oldorigin ); + + le->refEntity.reType = RT_MODEL; + // BFP - Allows to be seen while looking at the other side + // while the origin isn't present in camera + le->refEntity.nonNormalizedAxes = qtrue; + + le->color[0] = le->color[1] = le->color[2] = 1.0; + + le->startTime = cg.time - (rand() & 63); + le->lifeRate = duration; + le->endTime = le->startTime + le->lifeRate; + + // bias the time so all shader effects start correctly + le->refEntity.shaderTime = cg.time / 1000.0f; + + return le; +} + /* ================= @@ -525,3 +581,422 @@ void CG_BigExplode( vec3_t playerOrigin ) { CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); } + +/* +================= +CG_DebrisExplosion +================= +*/ +void CG_DebrisExplosion( vec3_t origin, vec3_t dir ) { // BFP - Debris particles explosion + int i; + vec3_t sprOrg, sprVel; + int numRocks = 26; + vec3_t right, up, forward; + + VectorCopy( dir, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // BFP - NOTE: Debris particles shouldn't be used for bullet and disk weapon types + + // BFP - TODO: Apply number of rocks as indicated on default.cfg file of some character: explosionRocks + + for ( i = 0; i < numRocks; ++i ) { + // spawn randomly the shaders with the particles + int shaderIndex = rand() % 3; + float speed = 1000.0f + (rand() % 800); + float rSpread = crandom() * 4000.0f; + float uSpread = crandom() * 4000.0f; + + // that would be the range for debris particles + VectorMA( origin, 24, dir, sprOrg ); + sprOrg[0] += (rand() % 24); + sprOrg[1] += (rand() % 24); + sprOrg[2] += (rand() % 24); + + VectorScale( dir, speed, sprVel ); + + VectorMA( sprVel, rSpread, right, sprVel ); + VectorMA( sprVel, uSpread, up, sprVel ); + + sprVel[0] += crandom() * 100; + sprVel[1] += crandom() * 100; + sprVel[2] += crandom() * 100; + + // BFP - Rock models + if ( cg_3dparticles.integer > 0 + && cgs.media.pebbleMdl1 + && cgs.media.pebbleMdl2 + && cgs.media.pebbleMdl3 ) { + int rockModelIndex = rand() % 3; + switch ( rockModelIndex ) { + case 0: { + CG_ParticleRockDebris( cgs.media.pebbleShader1, cgs.media.pebbleMdl1, sprOrg, sprVel, 3, 150, 250 ); + break; + } + case 1: { + CG_ParticleRockDebris( cgs.media.pebbleShader2, cgs.media.pebbleMdl2, sprOrg, sprVel, 3, 150, 250 ); + break; + } + default: { + CG_ParticleRockDebris( cgs.media.pebbleShader3, cgs.media.pebbleMdl3, sprOrg, sprVel, 3, 150, 250 ); + } + } + continue; + } + + switch ( shaderIndex ) { + case 0: { + CG_ParticleRockDebris( cgs.media.pebbleShader1, 0, sprOrg, sprVel, 3, 150, 250 ); + break; + } + case 1: { + CG_ParticleRockDebris( cgs.media.pebbleShader2, 0, sprOrg, sprVel, 3, 150, 250 ); + break; + } + default: { + CG_ParticleRockDebris( cgs.media.pebbleShader3, 0, sprOrg, sprVel, 3, 150, 250 ); + } + } + } +} + +/* +================= +CG_SparksExplosion +================= +*/ +void CG_SparksExplosion( vec3_t origin, vec3_t dir ) { // BFP - Spark particles explosion + int i; + vec3_t sparkOrg, sparkVel; + int numSparks = 26; + vec3_t right, up, forward; + + VectorCopy( dir, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // BFP - NOTE: Spark particles shouldn't be used on bullet and disk weapon types + + // BFP - TODO: Apply calling this function for finger blast and these rail gun weapon types when hitting a player + + // BFP - TODO: Apply number of sparks as indicated on default.cfg file of some character: explosionSparks + + for ( i = 0; i < numSparks; ++i ) { + // spawn randomly the shaders with the particles + int shaderIndex = (rand() % 100) < 50 ? 0 : 1; // if the random range was rand() % 2, it would repeat the pattern without randomize correctly + float speed = 2000.0f + (rand() % 800); + float rSpread = crandom() * 2000.0f; + float uSpread = crandom() * 2000.0f; + + VectorMA( origin, 1.0f, dir, sparkOrg ); + + VectorScale( dir, speed, sparkVel ); + + VectorMA( sparkVel, rSpread, right, sparkVel ); + VectorMA( sparkVel, uSpread, up, sparkVel ); + + sparkVel[0] += crandom() * 100; + sparkVel[1] += crandom() * 100; + sparkVel[2] += crandom() * 100; + + switch ( shaderIndex ) { + case 0: { + CG_ParticleSparks( cgs.media.sparkShader1, sparkOrg, sparkVel ); + break; + } + default: { + CG_ParticleSparks( cgs.media.sparkShader2, sparkOrg, sparkVel ); + } + } + } +} + +/* +================= +CG_BeamStruggleEffect +================= +*/ +void CG_BeamStruggleEffect( vec3_t origin, vec3_t dir ) { // BFP - Beam struggle effect + int i; + vec3_t sparkOrg, sparkVel; + int numSparks = 26; + + for ( i = 0; i < numSparks; ++i ) { + // spawn randomly the shaders with the particles + int shaderIndex = (rand() % 100) < 50 ? 0 : 1; + float speed = 600.0f + (rand() % 800); + + VectorMA( origin, 0, dir, sparkOrg ); + + sparkVel[0] = crandom(); + sparkVel[1] = crandom(); + sparkVel[2] = crandom(); + + VectorNormalize( sparkVel ); + VectorScale( sparkVel, speed, sparkVel ); + + switch ( shaderIndex ) { + case 0: { + CG_ParticleBeamStruggleSpark( cgs.media.sparkShader1, sparkOrg, sparkVel ); + break; + } + default: { + CG_ParticleBeamStruggleSpark( cgs.media.sparkShader2, sparkOrg, sparkVel ); + } + } + } +} + +/* +================= +CG_SmokeExplosion +================= +*/ +void CG_SmokeExplosion( vec3_t origin, vec3_t dir ) { // BFP - Explosion smoke + if ( cg_explosionSmoke.integer > 0 + && !( trap_CM_PointContents( origin, 0 ) & MASK_WATER ) ) { // don't spawn smoke under water, lava or any liquid + // BFP - TODO: Apply explosionSmoke as indicated on default.cfg file from some character: explosionSmoke + int i, numSmokes = 3; + // BFP - TODO: Apply explosionSmokeRadius as indicated on default.cfg file from some character: explosionSmokeRadius + int explosionSmokeRadius = 200; + // BFP - TODO: Apply explosionSmokeLife as indicated on default.cfg file from some character: explosionSmokeLife + int explosionSmokeLife = 1500; + // BFP - TODO: Apply explosionSmokeSpeed as indicated on default.cfg file from some character: explosionSmokeSpeed + int explosionSmokeSpeed = 10; + + // for spreading smoke + vec3_t up = {0, 0, 1}; + vec3_t right, forward; + + // BFP - To randomize the vertical speed + float minVert = 10 * explosionSmokeSpeed; + float maxVert = 50 * explosionSmokeSpeed; + + VectorCopy( dir, forward ); + CrossProduct( forward, up, right ); + if ( VectorLength( right ) < 0.1f ) { + vec3_t side = {1, 0, 0}; + CrossProduct( forward, side, right ); + } + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); + + for ( i = 0; i < numSmokes; ++i ) { + localEntity_t *leSmoke; + vec3_t vel, smokeOrg, spreadDir; + float rightSpread = crandom() * 500; + float upSpread = crandom() * 500; + + VectorCopy( dir, spreadDir ); + VectorMA( spreadDir, rightSpread, right, spreadDir ); + VectorMA( spreadDir, upSpread, up, spreadDir ); + VectorNormalize( spreadDir ); + + // position smoke offset in the spread direction + VectorMA( origin, 20 + ( rand() % 80 ), spreadDir, smokeOrg ); + + // velocity moves outward in spread direction + VectorScale( spreadDir, 150 + ( rand() % 500 ), vel ); + vel[2] = maxVert + ( rand() % (int)( minVert ) ); + + leSmoke = CG_SmokePuff( smokeOrg, vel, + explosionSmokeRadius, + 1, 1, 1, 0.33f, + explosionSmokeLife, + cg.time, 0, 0, + cgs.media.particleSmokeShader ); + + // change to this type, don't use the common smoke puff + leSmoke->leType = LE_MOVE_DONT_SCALE_FADE; + } + } +} + + +/* +================= +CG_ExplosionSound +================= +*/ +void CG_ExplosionSound( vec3_t origin ) { // BFP - Explosion sounds + // BFP - TODO: noExplosionSound from skin config + int i = rand() % 6; + + switch ( i ) { + case 0: trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.explosion1Sound ); break; + case 1: trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.explosion2Sound ); break; + case 2: trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.explosion3Sound ); break; + case 3: trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.explosion4Sound ); break; + case 4: trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.explosion5Sound ); break; + default: trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.explosion6Sound ); break; + } +} + + +/* +================= +CG_ExplosionEffect +================= +*/ +void CG_ExplosionEffect( vec3_t origin, vec3_t dir ) { // BFP - Explosion effects + // BFP - Low poly sphere + // BFP - TODO: Apply explosionModel from bfp attack config, highPolySphereModel is just a test + qhandle_t sphereModel = ( cg_lowpolysphere.integer > 0 ) ? cgs.media.lowPolySphereModel : cgs.media.highPolySphereModel; + localEntity_t *leSphere, *leRing = NULL, *leShell = NULL; + float scale = 1; + + // BFP - TODO: Apply explosionShader from bfp attack config, ImpactBeamExplosionShader is just a test + leSphere = CG_SpawnExplosionModel( origin, dir, LE_EXPLOSION_SPHERE, sphereModel, cgs.media.ImpactBeamExplosionShader, 1200 ); + if ( cg_explosionShell.integer > 0 ) { // BFP - Explosion shell + leShell = CG_SpawnExplosionModel( origin, dir, LE_EXPLOSION_SHELL, sphereModel, cgs.media.explosionShellShader, 250 ); + } + if ( cg_explosionRing.integer > 0 ) { // BFP - Explosion ring + leRing = CG_SpawnExplosionModel( origin, dir, LE_EXPLOSION_RING, cgs.media.ringFlashModel, cgs.media.railExplosionShader, 500 ); + } + CG_SmokeExplosion( origin, dir ); // BFP - Explosion smoke + + if ( cg_bigExplosions.integer > 0 ) { // BFP - Big explosions + const float MAX_SCALE = 25.0f, MAX_SCALEFACTOR = 6.0f; // limits to prevent too large scaling + int numPointsChargedOverMin = 1; // that means when reaching to 'READY!', it starts as 1 and if it's charging another charge point, adds 1 more + // BFP - TODO: Apply explosionScaleFactor as indicated on default.cfg file from some character: explosionScaleFactor + // BFP - TODO: Apply explosionScaleFactorChargeMult as indicated on default.cfg file from some character: explosionScaleFactorChargeMult + float explosionScaleFactor = 0.95, explosionScaleFactorChargeMult = 0; + // BFP - TODO: Apply explosionRingScaleFactor from default.cfg file from some character: explosionRingScaleFactor + // BFP - TODO: Apply explosionRingScaleFactorChargeMult from default.cfg file from some character: explosionRingScaleFactorChargeMult + float explosionRingScaleFactor = 0.95, explosionRingScaleFactorChargeMult = 0; + // BFP - TODO: Apply explosionShellScaleFactor from default.cfg file from some character: explosionShellScaleFactor + // BFP - TODO: Apply explosionShellScaleFactorChargeMult from default.cfg file from some character: explosionShellScaleFactorChargeMult + float explosionShellScaleFactor = 0.95, explosionShellScaleFactorChargeMult = 0; + + if ( explosionScaleFactor > MAX_SCALEFACTOR ) explosionScaleFactor = MAX_SCALEFACTOR; + if ( explosionScaleFactorChargeMult > MAX_SCALEFACTOR ) explosionScaleFactorChargeMult = MAX_SCALEFACTOR; + scale = explosionScaleFactor + explosionScaleFactorChargeMult * numPointsChargedOverMin; + if ( scale > MAX_SCALE ) scale = MAX_SCALE; + VectorScale( leSphere->refEntity.axis[0], scale, leSphere->refEntity.axis[0] ); + VectorScale( leSphere->refEntity.axis[1], scale, leSphere->refEntity.axis[1] ); + VectorScale( leSphere->refEntity.axis[2], scale, leSphere->refEntity.axis[2] ); + + if ( cg_explosionRing.integer > 0 ) { + if ( explosionRingScaleFactor > MAX_SCALEFACTOR ) explosionRingScaleFactor = MAX_SCALEFACTOR; + if ( explosionRingScaleFactorChargeMult > MAX_SCALEFACTOR ) explosionRingScaleFactorChargeMult = MAX_SCALEFACTOR; + scale = explosionRingScaleFactor + explosionRingScaleFactorChargeMult * numPointsChargedOverMin; + if ( scale > MAX_SCALE ) scale = MAX_SCALE; + if ( leRing ) { + VectorScale( leRing->refEntity.axis[0], scale, leRing->refEntity.axis[0] ); + VectorScale( leRing->refEntity.axis[1], scale, leRing->refEntity.axis[1] ); + VectorScale( leRing->refEntity.axis[2], scale, leRing->refEntity.axis[2] ); + } + } + + if ( cg_explosionShell.integer > 0 ) { + if ( explosionShellScaleFactor > MAX_SCALEFACTOR ) explosionShellScaleFactor = MAX_SCALEFACTOR; + if ( explosionShellScaleFactorChargeMult > MAX_SCALEFACTOR ) explosionShellScaleFactorChargeMult = MAX_SCALEFACTOR; + scale = explosionShellScaleFactor + explosionShellScaleFactorChargeMult * numPointsChargedOverMin; + if ( scale > MAX_SCALE ) scale = MAX_SCALE; + if ( leShell ) { + VectorScale( leShell->refEntity.axis[0], scale, leShell->refEntity.axis[0] ); + VectorScale( leShell->refEntity.axis[1], scale, leShell->refEntity.axis[1] ); + VectorScale( leShell->refEntity.axis[2], scale, leShell->refEntity.axis[2] ); + } + } + } + + // BFP - Apply dynamic explosion light values + leSphere->light = 400 * scale; + leSphere->lightColor[0] = 1; + leSphere->lightColor[1] = 0.75; + leSphere->lightColor[2] = 0.0; +} + + +/* +================= +CG_ForceFieldEffect +================= +*/ +void CG_ForceFieldEffect( centity_t *cent, vec3_t origin, char *explosionModel, char *explosionShader ) { // BFP - Forcefield effect + if ( cent->currentState.weapon == WP_SHOTGUN + && ( cent->currentState.eFlags & EF_FIRING ) ) { + if ( !cent->pe.forceFieldStartTime ) { + cent->pe.forceFieldStartTime = cg.time; + } + } else { + cent->pe.forceFieldStartTime = 0; + } + + if ( cent->pe.forceFieldStartTime ) { + const float MAX_SCALE = 20; + int elapsed = cg.time - cent->pe.forceFieldStartTime; + float scale; + // BFP - TODO: Apply explosionModel from bfp attack config + qhandle_t explosionModelRef = trap_R_RegisterModel( explosionModel ); + // BFP - TODO: Apply explosionShader from bfp attack config + qhandle_t explosionShaderRef = trap_R_RegisterShader( explosionShader ); + // BFP - TODO: Apply explosionScaleFactor as indicated on default.cfg file from some character: explosionScaleFactor + // BFP - TODO: Apply explosionScaleFactorChargeMult as indicated on default.cfg file from some character: explosionScaleFactorChargeMult + float explosionScaleFactor = 1, explosionScaleFactorChargeMult = 2; + refEntity_t ff; + + memset( &ff, 0, sizeof(ff) ); + + ff.reType = RT_MODEL; + ff.hModel = explosionModelRef; + ff.customShader = explosionShaderRef; + ff.renderfx = RF_LIGHTING_ORIGIN; + + VectorCopy( origin, ff.origin ); + VectorCopy( origin, ff.lightingOrigin ); + AnglesToAxis( vec3_origin, ff.axis ); + + if ( explosionScaleFactor <= 0 ) { + explosionScaleFactor = 1; + } + +// BFP - That version scales smoothly than the original BFP one +#define FORCEFIELD_SMOOTH_SCALE 1 +#if FORCEFIELD_SMOOTH_SCALE + { + int endTime = cent->pe.forceFieldStartTime + 5400; + int duration = endTime - cent->pe.forceFieldStartTime; + const float TIMESCALE_FF = 13; + float t = (float)(elapsed * TIMESCALE_FF) / (float)duration; + + if ( t > 1 ) { + t = 1; + } + scale = ( explosionScaleFactor + explosionScaleFactorChargeMult ) + * ( 1.0f + t * ( MAX_SCALE - 1.0f ) ); + } +#else + { + const int MAX_STEPS = 27; + const int STEP_TIME_MSC = 200; // msc per step + int step = elapsed / STEP_TIME_MSC; + + if ( step > MAX_STEPS ) { + step = MAX_STEPS; + } + //scale = 1.0f + (float)step / (float)MAX_STEPS * ( MAX_SCALE - 1.0f ); + + //scale = ( explosionScaleFactor + explosionScaleFactorChargeMult ) + // * ( 1.0f + (float)step / MAX_STEPS * ( MAX_SCALE - 1.0f ) ); + + scale = 1.0f + ( (float)step / (float)MAX_STEPS ) * MAX_SCALE + * ( explosionScaleFactor + explosionScaleFactorChargeMult ); + } +#endif + // BFP - Allows to be seen while looking at the other side + // while the origin isn't present in camera + ff.nonNormalizedAxes = qtrue; + + CG_ModelSize( &ff, scale ); + + // add the entity + trap_R_AddRefEntityToScene( &ff ); + + // add the dlight + trap_R_AddLightToScene( ff.origin, 600 * scale, 0.25, 0.15, 0.75 ); + } +} diff --git a/source/cgame/cg_ents.c b/source/cgame/cg_ents.c index 98f3190..23354d4 100644 --- a/source/cgame/cg_ents.c +++ b/source/cgame/cg_ents.c @@ -426,11 +426,33 @@ static void CG_Missile( centity_t *cent ) { VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); - if ( cent->currentState.weapon == WP_PLASMAGUN ) { + // BFP - missileShader + if ( weapon->missileShader ) { + ent.customShader = weapon->missileShader; + } + + // BFP - missileRotation + if ( weapon->missileRotation > 0 ) { + ent.rotation = weapon->missileRotation; + } + + // BFP - missileModel and missileShader + if ( !weapon->missileModel && weapon->missileShader ) { ent.reType = RT_SPRITE; - ent.radius = 16; - ent.rotation = 0; - ent.customShader = cgs.media.plasmaBallShader; + + // BFP - missileRadius and missileRadiusChargeMult + if ( ent.reType == RT_SPRITE ) { + float missileRadius = 50, missileRadiusChargeMult = 75; + float minCharge = 2; + float totalCharge = cent->currentState.generic1 - minCharge; + if ( totalCharge < 0 ) { + totalCharge = 0; + } + if ( missileRadius > 0 ) { + ent.radius = missileRadius + missileRadiusChargeMult * totalCharge; + } + } + trap_R_AddRefEntityToScene( &ent ); return; } @@ -438,6 +460,10 @@ static void CG_Missile( centity_t *cent ) { // flicker between two skins ent.skinNum = cg.clientFrame & 1; ent.hModel = weapon->missileModel; + // BFP - Skip rendering if no missile model + if ( !weapon->missileModel ) { + return; + } ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; // convert direction of travel into axis @@ -446,18 +472,46 @@ static void CG_Missile( centity_t *cent ) { } // spin as it moves - if ( s1->pos.trType != TR_STATIONARY ) { - RotateAroundDirection( ent.axis, cg.time / 4 ); + if ( s1->pos.trType != TR_STATIONARY + && s1->weapon != WP_BFG ) { // BFP - For disk or missileSpinHoriz (qboolean) weapons, don't rotate like the rocket + // BFP - missileModelRotation + float missileModelRotation = 0.2; + RotateAroundDirection( ent.axis, cg.time * missileModelRotation ); } else { { + // BFP - Rotate Z-axis like a wheel + vec3_t temp; RotateAroundDirection( ent.axis, s1->time ); + + VectorCopy( ent.axis[0], temp ); + RotatePointAroundVector( ent.axis[0], ent.axis[2], temp, cg.autoAnglesFast[1] ); + VectorCopy( ent.axis[1], temp ); + RotatePointAroundVector( ent.axis[1], ent.axis[2], temp, cg.autoAnglesFast[1] ); + } + } + + // BFP - missileScaleFactor and missileScaleFactorChargeMult + // BFP - TODO: Handle saved ki charge points and calculate the total of number of points charged over the min + if ( ent.reType == RT_MODEL ) { + float scale = 1; + float missileScaleFactor = 3, missileScaleFactorChargeMult = 1; + float minCharge = 2; + float totalCharge = cent->currentState.generic1 - minCharge; + if ( totalCharge < 0 ) { + totalCharge = 0; } + if ( missileScaleFactor > 0 ) { + scale = missileScaleFactor + missileScaleFactorChargeMult * totalCharge; + } + CG_ModelSize( &ent, scale ); } // add to refresh list, possibly with quad glow - CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); + CG_AddRefEntityWithPowerups( ent, s1, TEAM_FREE ); } +// BFP - no hook +#if 0 /* =============== CG_Grapple @@ -506,6 +560,7 @@ static void CG_Grapple( centity_t *cent ) { trap_R_AddRefEntityToScene( &ent ); } +#endif /* =============== @@ -618,7 +673,7 @@ Also called by client movement prediction code void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; - vec3_t oldAngles, angles, deltaAngles; + vec3_t oldAngles, angles; //, deltaAngles; if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { VectorCopy( in, out ); @@ -638,7 +693,8 @@ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); VectorSubtract( origin, oldOrigin, deltaOrigin ); - VectorSubtract( angles, oldAngles, deltaAngles ); + // BFP - Unused in Q3? What? + // VectorSubtract( angles, oldAngles, deltaAngles ); VectorAdd( in, deltaOrigin, out ); @@ -743,9 +799,6 @@ static void CG_TeamBase( centity_t *cent ) { else if ( cent->currentState.modelindex == TEAM_BLUE ) { model.hModel = cgs.media.blueFlagBaseModel; } - else { - model.hModel = cgs.media.neutralFlagBaseModel; - } trap_R_AddRefEntityToScene( &model ); } } diff --git a/source/cgame/cg_event.c b/source/cgame/cg_event.c index dc3c179..79b2b3b 100644 --- a/source/cgame/cg_event.c +++ b/source/cgame/cg_event.c @@ -83,8 +83,8 @@ static void CG_Obituary( entityState_t *ent ) { const char *attackerInfo; char targetName[32]; char attackerName[32]; - gender_t gender; - clientInfo_t *ci; + // BFP - No gender variable for MOD messages + // gender_t gender; target = ent->otherEntityNum; attacker = ent->otherEntityNum2; @@ -93,7 +93,6 @@ static void CG_Obituary( entityState_t *ent ) { if ( target < 0 || target >= MAX_CLIENTS ) { CG_Error( "CG_Obituary: target out of range" ); } - ci = &cgs.clientinfo[target]; if ( attacker < 0 || attacker >= MAX_CLIENTS ) { attacker = ENTITYNUM_WORLD; @@ -138,12 +137,18 @@ static void CG_Obituary( entityState_t *ent ) { case MOD_TRIGGER_HURT: message = "was in the wrong place"; break; + // BFP - Player being kicked by using an illegal model + case MOD_ILLEGAL_PLAYER_MODEL: + message = "got kicked into spectator mode for using an illegal model"; + break; default: message = NULL; break; } if (attacker == target) { + // BFP - No gender MOD messages +#if 0 gender = ci->gender; switch (mod) { case MOD_GRENADE_SPLASH: @@ -182,6 +187,10 @@ static void CG_Obituary( entityState_t *ent ) { message = "killed himself"; break; } +#endif + if ( attacker == cg.snap->ps.clientNum ) { // BFP - Add 1 lifedeath in the history + trap_Cvar_Set( "cg_lifedeaths", va( "%i", (int)( cg_lifedeaths.integer + 1 ) ) ); + } } if (message) { @@ -190,18 +199,20 @@ static void CG_Obituary( entityState_t *ent ) { } // check for kill messages from the current clientNum - if ( attacker == cg.snap->ps.clientNum ) { + if ( attacker == cg.snap->ps.clientNum && attacker != target ) { char *s; - if ( cgs.gametype < GT_TEAM ) { - s = va("You fragged %s\n%s place with %i", targetName, + if ( cgs.gametype < GT_TEAM ) { // BFP - Before Q3: "You fragged %s\n%s place with %i" + s = va("You sent %s to the next dimension!\n%s place with %i", targetName, CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), cg.snap->ps.persistant[PERS_SCORE] ); - } else { - s = va("You fragged %s", targetName ); + } else { // BFP - Before Q3: "You fragged %s" + s = va("You sent %s to the next dimension!", targetName ); } CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); // print the text message as well + // BFP - Add 1 lifekill in the history + trap_Cvar_Set( "cg_lifekills", va( "%i", (int)( cg_lifekills.integer + 1 ) ) ); } // check for double client messages @@ -217,8 +228,11 @@ static void CG_Obituary( entityState_t *ent ) { } } - if ( attacker != ENTITYNUM_WORLD ) { + // BFP - Don't admit that the attacker is the same as the target + if ( attacker != ENTITYNUM_WORLD && attacker != target ) { switch (mod) { + // BFP - No other MOD messages +#if 0 case MOD_GRAPPLE: message = "was caught by"; break; @@ -270,11 +284,18 @@ static void CG_Obituary( entityState_t *ent ) { message = "tried to invade"; message2 = "'s personal space"; break; - default: - message = "was killed by"; +#endif + case MOD_MELEE: // BFP - Melee + message = "was beaten up by"; + break; + default: // BFP - Kill with ki attack and telefrag message + message = "was sent to the next dimension by"; break; } + if ( target == cg.snap->ps.clientNum ) { // BFP - Add 1 lifedeath in the history + trap_Cvar_Set( "cg_lifedeaths", va( "%i", (int)( cg_lifedeaths.integer + 1 ) ) ); + } if (message) { CG_Printf( "%s %s %s%s\n", targetName, message, attackerName, message2); @@ -284,6 +305,11 @@ static void CG_Obituary( entityState_t *ent ) { // we don't know what it was CG_Printf( "%s died.\n", targetName ); + if ( target == cg.snap->ps.clientNum ) { // BFP - Add 1 lifedeath in the history + trap_Cvar_Set( "cg_lifedeaths", va( "%i", (int)( cg_lifedeaths.integer + 1 ) ) ); + } + // BFP - Reset ki trails to avoid viewing other trails + CG_ResetTrail( KI_TRAIL, target, vec3_origin ); } //========================================================================== @@ -402,109 +428,158 @@ An entity has an event value also called by CG_CheckPlayerstateEvents ============== */ -#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} -void CG_EntityEvent( centity_t *cent, vec3_t position ) { +void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { entityState_t *es; - int event; + entity_event_t event; vec3_t dir; const char *s; int clientNum; clientInfo_t *ci; + centity_t *ce; es = ¢->currentState; event = es->event & ~EV_EVENT_BITS; + if ( (unsigned) event >= EV_MAX ) { + CG_Error( "Unknown event: %i", event ); + return; + } + if ( cg_debugEvents.integer ) { - CG_Printf( "ent:%3i event:%3i ", es->number, event ); + CG_Printf( "ent:%3i event:%3i %s\n", es->number, event, eventnames[ event ] ); } if ( !event ) { - DEBUGNAME("ZEROEVENT"); + // DEBUGNAME("ZEROEVENT"); return; } clientNum = es->clientNum; - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + if ( (unsigned) clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; + // BFP - HIGHLY MODIFIED, every event is sorted for original BFP networking + switch ( event ) { + case EV_NONE: // 0 + case EV_UNUSED_INDEX1: // 1 + case EV_UNUSED_INDEX2: // 2 + case EV_UNUSED_INDEX3: // 3 + case EV_UNUSED_INDEX4: // 4 + case EV_UNUSED_INDEX5: // 5 + case EV_UNUSED_INDEX6: // 6 + case EV_UNUSED_INDEX7: // 7 + case EV_UNUSED_INDEX8: // 8 + case EV_UNUSED_INDEX9: // 9 + break; + + // BFP - Melee + case EV_MELEE_READY: // 10 + break; + case EV_MELEE: // 11 + { + int rndMeleeSnd = rand() % 5; + switch ( rndMeleeSnd ) { + case 0: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit1.wav" ) ); + break; + } + case 1: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit2.wav" ) ); + break; + } + case 2: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit3.wav" ) ); + break; + } + case 3: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit4.wav" ) ); + break; + } + default: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit5.wav" ) ); + } + } + break; + } + + case EV_UNUSED_INDEX12: // 12 + break; + + // BFP - Tier up events + case EV_TIER_RESET: // 13 + break; + case EV_TIER_0: // 14 + case EV_TIER_1: // 15 + case EV_TIER_2: // 16 + case EV_TIER_3: // 17 + case EV_TIER_4: // 18 + trap_S_StartSound ( NULL, es->otherEntityNum, CHAN_BODY, cgs.media.tierUpSound ); + if ( event == EV_TIER_4 && es->otherEntityNum == cg.snap->ps.clientNum ) { + trap_SendConsoleCommand( "transformorbit\n" ); + } + break; + + // BFP - Short-Range Teleport (Zanzoken) + case EV_ZANZOKEN_IN: // 19 + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/srteleport.wav" ) ); + case EV_ZANZOKEN_OUT: // 20 + break; + + // BFP - Ki boost + case EV_KI_BOOST: // 21 + break; + + // BFP - A normal jump sound is played when enables the flight + case EV_ENABLE_FLIGHT: // 22 + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/jump1.wav" ) ); + break; + // // movement generated events // - case EV_FOOTSTEP: - DEBUGNAME("EV_FOOTSTEP"); + case EV_FOOTSTEP: // 23 if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ ci->footsteps ][rand()&3] ); } break; - case EV_FOOTSTEP_METAL: - DEBUGNAME("EV_FOOTSTEP_METAL"); + + case EV_FOOTSTEP_METAL: // 24 if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); } break; - case EV_FOOTSPLASH: - DEBUGNAME("EV_FOOTSPLASH"); + + case EV_FOOTSPLASH: // 25 if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); } break; - case EV_FOOTWADE: - DEBUGNAME("EV_FOOTWADE"); + + case EV_FOOTWADE: // 26 if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); } break; - case EV_SWIM: - DEBUGNAME("EV_SWIM"); + + case EV_SWIM: // 27 if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); } break; - - case EV_FALL_SHORT: - DEBUGNAME("EV_FALL_SHORT"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -8; - cg.landTime = cg.time; - } - break; - case EV_FALL_MEDIUM: - DEBUGNAME("EV_FALL_MEDIUM"); - // use normal pain sound - trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -16; - cg.landTime = cg.time; - } - break; - case EV_FALL_FAR: - DEBUGNAME("EV_FALL_FAR"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); - cent->pe.painTime = cg.time; // don't play a pain sound right after this - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -24; - cg.landTime = cg.time; - } - break; - - case EV_STEP_4: - case EV_STEP_8: - case EV_STEP_12: + case EV_STEP_4: // 28 + case EV_STEP_8: // 29 + case EV_STEP_12: // 30 + // 31 case EV_STEP_16: // smooth out step up transitions - DEBUGNAME("EV_STEP"); { float oldStep; int delta; @@ -536,9 +611,41 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { break; } - case EV_JUMP_PAD: - DEBUGNAME("EV_JUMP_PAD"); + case EV_FALL_SHORT: // 32 + 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: // 33 + // BFP - Use normal land sound instead + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + // 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: // 34 + 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_JUMP_PAD: // 35 // CG_Printf( "EV_JUMP_PAD w/effect #%i\n", es->eventParm ); +// BFP - No smoke puff effect when using a jump pad +#if 0 { localEntity_t *smoke; vec3_t up = {0, 0, 1}; @@ -552,39 +659,91 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { LEF_PUFF_DONT_SCALE, cgs.media.smokePuffShader ); } +#endif // 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, "sound/bfp/jump1.wav" ) ); // BFP - Normal jump sound + // BFP - Q3 jump sound removed and no break after this case, so continue to BFP jump sound break; - case EV_JUMP: - DEBUGNAME("EV_JUMP"); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "sound/bfp/jump1.wav" ) ); // BFP - Normal jump sound - break; - case EV_TAUNT: - DEBUGNAME("EV_TAUNT"); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + case EV_JUMP: // 36 + case EV_JUMP_2: // 37 + // BFP - Use the second jump sound when using ki boost only when it isn't flying + if ( ( es->eFlags & EF_AURA ) && !( cent->currentState.eFlags & EF_FLIGHT ) ) { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "sound/bfp/jump2.wav" ) ); // BFP - Ki boost jump sound + } else { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "sound/bfp/jump1.wav" ) ); // BFP - Normal jump sound + } break; - case EV_WATER_TOUCH: - DEBUGNAME("EV_WATER_TOUCH"); + + + case EV_WATER_TOUCH: // 38 trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); break; - case EV_WATER_LEAVE: - DEBUGNAME("EV_WATER_LEAVE"); + + case EV_WATER_LEAVE: // 39 trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); break; - case EV_WATER_UNDER: - DEBUGNAME("EV_WATER_UNDER"); + + case EV_WATER_UNDER: // 40 trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + // BFP - Bubble and splash particles when entering under water + { + vec3_t end = {0, 0, 1}; + vec3_t splashOrigin; + float bubbleSize = 2; + float bubbleRange = 20; + float debrisSize = 3; + float velocity = 150; + float accel = 250; + + VectorCopy( cent->lerpOrigin, splashOrigin ); + splashOrigin[2] += 20; // place a bit above + + // that would be the range + splashOrigin[0] += (crandom() * 5); + splashOrigin[1] += (crandom() * 5); + + // BFP - Monster gamemode, player monster particle size and positions + if ( cgs.gametype == GT_MONSTER + && ( cent->currentState.eFlags & EF_MONSTER ) ) { + bubbleSize = 8; + bubbleRange = 100; + debrisSize = 8; + velocity = 400; + accel = 700; + } + + // Splash! + // BFP - NOTE: These are not debris :P + CG_ParticleWaterSplash( cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, debrisSize, velocity, accel ); + CG_ParticleWaterSplash( cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, debrisSize, velocity, accel ); + CG_ParticleWaterSplash( cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, debrisSize, velocity, accel ); + CG_ParticleWaterSplash( cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, debrisSize, velocity, accel ); + CG_ParticleWaterSplash( cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, debrisSize, velocity, accel ); + + splashOrigin[2] -= 25; // place a bit below + // BFP - Monster gamemode, player monster particle size and positions + if ( cgs.gametype == GT_MONSTER + && ( cent->currentState.eFlags & EF_MONSTER ) ) { + splashOrigin[2] -= 100; // place a bit below + } + + // Blub, blub, blub... + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, splashOrigin, end, 700, bubbleRange, bubbleSize ); + } break; - case EV_WATER_CLEAR: - DEBUGNAME("EV_WATER_CLEAR"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + + case EV_WATER_CLEAR: // 41 + // BFP - No water clear sound + // trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); break; - case EV_ITEM_PICKUP: - DEBUGNAME("EV_ITEM_PICKUP"); + case EV_ITEM_PICKUP: // 42 { gitem_t *item; int index; @@ -594,6 +753,17 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { if ( index < 1 || index >= bg_numItems ) { break; } + + if ( entityNum >= 0 ) { + // our predicted entity + ce = cg_entities + entityNum; + if ( ce->delaySpawn > cg.time && ce->delaySpawnPlayed ) { + break; // delay item pickup + } + } else { + ce = NULL; + } + item = &bg_itemlist[ index ]; // powerups and team items will have a separate global sound, this one @@ -609,11 +779,14 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { if ( es->number == cg.snap->ps.clientNum ) { CG_ItemPickup( index ); } + + if ( ce ) { + ce->delaySpawnPlayed = qtrue; + } } break; - case EV_GLOBAL_ITEM_PICKUP: - DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); + case EV_GLOBAL_ITEM_PICKUP: // 43 { gitem_t *item; int index; @@ -623,6 +796,17 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { if ( index < 1 || index >= bg_numItems ) { break; } + + if ( entityNum >= 0 ) { + // our predicted entity + ce = cg_entities + entityNum; + if ( ce->delaySpawn > cg.time && ce->delaySpawnPlayed ) { + break; + } + } else { + ce = NULL; + } + item = &bg_itemlist[ index ]; // powerup pickups are global if( item->pickup_sound ) { @@ -633,86 +817,50 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { if ( es->number == cg.snap->ps.clientNum ) { CG_ItemPickup( index ); } + + if ( ce ) { + ce->delaySpawnPlayed = qtrue; + } } break; // // weapon events // - case EV_NOAMMO: - DEBUGNAME("EV_NOAMMO"); + case EV_NOAMMO: // 44 // trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); if ( es->number == cg.snap->ps.clientNum ) { CG_OutOfAmmoChange(); } break; - case EV_CHANGE_WEAPON: - DEBUGNAME("EV_CHANGE_WEAPON"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + + case EV_CHANGE_WEAPON: // 45 + // BFP - Don't play select sound to other players and spectators, only the player itself + if ( es->number == cg.snap->ps.clientNum && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + } break; - case EV_FIRE_WEAPON: - DEBUGNAME("EV_FIRE_WEAPON"); + + case EV_FIRE_WEAPON: // 46 CG_FireWeapon( cent ); break; - case EV_USE_ITEM0: - DEBUGNAME("EV_USE_ITEM0"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM1: - DEBUGNAME("EV_USE_ITEM1"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM2: - DEBUGNAME("EV_USE_ITEM2"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM3: - DEBUGNAME("EV_USE_ITEM3"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM4: - DEBUGNAME("EV_USE_ITEM4"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM5: - DEBUGNAME("EV_USE_ITEM5"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM6: - DEBUGNAME("EV_USE_ITEM6"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM7: - DEBUGNAME("EV_USE_ITEM7"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM8: - DEBUGNAME("EV_USE_ITEM8"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM9: - DEBUGNAME("EV_USE_ITEM9"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM10: - DEBUGNAME("EV_USE_ITEM10"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM11: - DEBUGNAME("EV_USE_ITEM11"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM12: - DEBUGNAME("EV_USE_ITEM12"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM13: - DEBUGNAME("EV_USE_ITEM13"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM14: - DEBUGNAME("EV_USE_ITEM14"); + case EV_USE_ITEM0: // 47 + case EV_USE_ITEM1: // 48 + case EV_USE_ITEM2: // 49 + case EV_USE_ITEM3: // 50 + case EV_USE_ITEM4: // 51 + case EV_USE_ITEM5: // 52 + case EV_USE_ITEM6: // 53 + case EV_USE_ITEM7: // 54 + case EV_USE_ITEM8: // 55 + case EV_USE_ITEM9: // 56 + case EV_USE_ITEM10: // 57 + case EV_USE_ITEM11: // 58 + case EV_USE_ITEM12: // 59 + case EV_USE_ITEM13: // 60 + case EV_USE_ITEM14: // 61 + case EV_USE_ITEM15: // 62 CG_UseItem( cent ); break; @@ -721,91 +869,34 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { // // 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); + case EV_ITEM_RESPAWN: // 63 + cent->miscTime = cg.time; // scale up from this + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); 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); + case EV_ITEM_POP: // 64 + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); break; - case EV_ITEM_POP: - DEBUGNAME("EV_ITEM_POP"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + case EV_PLAYER_TELEPORT_IN: // 65 + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + CG_SpawnEffect( position, cg.snap->ps.clientNum == clientNum && cg_thirdPerson.integer == 0 ); 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 ); + + case EV_PLAYER_TELEPORT_OUT: // 66 + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + CG_SpawnEffect( position, qfalse ); break; - case EV_GRENADE_BOUNCE: - DEBUGNAME("EV_GRENADE_BOUNCE"); + case EV_GRENADE_BOUNCE: // 67 if ( rand() & 1 ) { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb1aSound ); } else { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb2aSound ); } break; - case EV_SCOREPLUM: - DEBUGNAME("EV_SCOREPLUM"); - CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); - break; - - // - // missile impacts - // - case EV_MISSILE_HIT: - DEBUGNAME("EV_MISSILE_HIT"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); - break; - - case EV_MISSILE_MISS: - DEBUGNAME("EV_MISSILE_MISS"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); - break; - - case EV_MISSILE_MISS_METAL: - DEBUGNAME("EV_MISSILE_MISS_METAL"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); - break; - - case EV_RAILTRAIL: - DEBUGNAME("EV_RAILTRAIL"); - cent->currentState.weapon = WP_RAILGUN; - // if the end was on a nomark surface, don't make an explosion - CG_RailTrail( ci, es->origin2, es->pos.trBase ); - if ( es->eventParm != 255 ) { - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, es->clientNum, position, dir, IMPACTSOUND_DEFAULT ); - } - break; - - 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 ); - break; - - case EV_BULLET_HIT_FLESH: - DEBUGNAME("EV_BULLET_HIT_FLESH"); - CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); - break; - - case EV_SHOTGUN: - DEBUGNAME("EV_SHOTGUN"); - CG_ShotgunFire( es ); - break; - case EV_GENERAL_SOUND: - DEBUGNAME("EV_GENERAL_SOUND"); + case EV_GENERAL_SOUND: // 68 if ( cgs.gameSounds[ es->eventParm ] ) { trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); } else { @@ -813,9 +904,8 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); } break; - + // 69 case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes - DEBUGNAME("EV_GLOBAL_SOUND"); if ( cgs.gameSounds[ es->eventParm ] ) { trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); } else { @@ -823,25 +913,24 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); } break; - + // 70 case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes { - DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); switch( es->eventParm ) { case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag - if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) CG_AddBufferedSound( cgs.media.captureYourTeamSound ); else CG_AddBufferedSound( cgs.media.captureOpponentSound ); break; case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag - if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) CG_AddBufferedSound( cgs.media.captureYourTeamSound ); else CG_AddBufferedSound( cgs.media.captureOpponentSound ); break; case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used - if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) CG_AddBufferedSound( cgs.media.returnYourTeamSound ); else CG_AddBufferedSound( cgs.media.returnOpponentSound ); @@ -849,7 +938,7 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); break; case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned - if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) CG_AddBufferedSound( cgs.media.returnYourTeamSound ); else CG_AddBufferedSound( cgs.media.returnOpponentSound ); @@ -859,40 +948,29 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag // if this player picked up the flag then a sound is played in CG_CheckLocalSounds - if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + if ( cg.snap->ps.powerups[PW_BLUEFLAG] ) { } else { - if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); } - else if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { + else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); } } break; case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag // if this player picked up the flag then a sound is played in CG_CheckLocalSounds - if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { - } - else { - if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { + if ( cg.snap->ps.powerups[PW_REDFLAG] ) { + } else { + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); } - else if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { + else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); } } break; - case GTS_REDOBELISK_ATTACKED: // Overload: red obelisk is being attacked - if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { - CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); - } - break; - case GTS_BLUEOBELISK_ATTACKED: // Overload: blue obelisk is being attacked - if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { - CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); - } - break; case GTS_REDTEAM_SCORED: CG_AddBufferedSound(cgs.media.redScoredSound); @@ -915,85 +993,168 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { break; } - case EV_PAIN: + case EV_BULLET_HIT_FLESH: // 71 + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); + break; + + case EV_BULLET_HIT_WALL: // 72 + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); + break; + + // + // missile impacts + // + case EV_MISSILE_HIT: // 73 + ByteToDir( es->eventParm, dir ); + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); + CG_ResetTrail( BEAM_TRAIL, es->number, es->origin ); // BFP - Reset beam trail + break; + + case EV_MISSILE_MISS: // 74 + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); + if ( es->weapon != WP_MACHINEGUN && es->weapon != WP_SHOTGUN ) { // BFP - Avoid exploding using finger blast type thingies + // BFP - Debris particles explosion + CG_DebrisExplosion( position, dir ); + // BFP - Spark particles explosion + CG_SparksExplosion( position, dir ); + } + CG_ResetTrail( BEAM_TRAIL, es->number, es->origin ); // BFP - Reset beam trail + break; + + case EV_MISSILE_MISS_METAL: // 75 + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); + // BFP - Debris particles explosion + CG_DebrisExplosion( position, dir ); + // BFP - Spark particles explosion + CG_SparksExplosion( position, dir ); + CG_ResetTrail( BEAM_TRAIL, es->number, es->origin ); // BFP - Reset beam trail + break; + + // BFP - EV_MISSILE_DETONATE - used on ki grenade bounces and beams, + // that happens when projectiles/beams reaches their lifetime limit or are stopped by the player actions + // no debris and sparks particles here + case EV_MISSILE_DETONATE: // 76 + { + vec3_t dirDetonate = {0, 0, 1}; // place the explosion position and size correctly + CG_MissileHitWall( es->weapon, 0, position, dirDetonate, IMPACTSOUND_DEFAULT ); + CG_ResetTrail( BEAM_TRAIL, es->number, es->origin ); // BFP - Reset beam trail + } + break; + + case EV_RAILTRAIL: // 77 + cent->currentState.weapon = WP_RAILGUN; + // if the end was on a nomark surface, don't make an explosion + CG_RailTrail( ci, es->origin2, es->pos.trBase ); + ByteToDir( es->eventParm, dir ); + break; + + case EV_SHOTGUN: // 78 + // BFP - No shotgun fire, just force field test + // CG_ShotgunFire( es ); + break; + + case EV_UNUSED_INDEX79: // 79 + break; + + case EV_PAIN: // 80 // local player sounds are triggered in CG_CheckLocalSounds, // so ignore events on the player - DEBUGNAME("EV_PAIN"); if ( cent->currentState.number != cg.snap->ps.clientNum ) { CG_PainEvent( cent, es->eventParm ); } break; - case EV_DEATH1: - case EV_DEATH2: - case EV_DEATH3: - DEBUGNAME("EV_DEATHx"); + case EV_DEATH1: // 81 + case EV_DEATH2: // 82 + case EV_DEATH3: // 83 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"); + case EV_OBITUARY: // 84 CG_Obituary( es ); break; // // powerup events // - case EV_POWERUP_QUAD: - DEBUGNAME("EV_POWERUP_QUAD"); + case EV_POWERUP_QUAD: // 85 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"); + + case EV_POWERUP_BATTLESUIT: // 86 + // BFP - No battlesuit powerup +#if 0 if ( es->number == cg.snap->ps.clientNum ) { cg.powerupActive = PW_BATTLESUIT; cg.powerupTime = cg.time; } +#endif trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); break; - case EV_POWERUP_REGEN: - DEBUGNAME("EV_POWERUP_REGEN"); - if ( es->number == cg.snap->ps.clientNum ) { - cg.powerupActive = PW_REGEN; - cg.powerupTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); + + case EV_UNUSED_INDEX87: // 87 break; - case EV_GIB_PLAYER: - DEBUGNAME("EV_GIB_PLAYER"); - // don't play gib sound when using the kamikaze because it interferes - // with the kamikaze sound, downside is that the gib sound will also - // not be played when someone is gibbed while just carrying the kamikaze - if ( !(es->eFlags & EF_KAMIKAZE) ) { - trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); - } + case EV_GIB_PLAYER: // 88 + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); CG_GibPlayer( cent->lerpOrigin ); break; - case EV_STOPLOOPINGSOUND: - DEBUGNAME("EV_STOPLOOPINGSOUND"); + case EV_UNUSED_INDEX89: // 89 + case EV_UNUSED_INDEX90: // 90 + break; + + case EV_SCOREPLUM: // 91 + CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); + break; + + case EV_TAUNT: // 92 + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + break; + + case EV_UNUSED_INDEX93: // 93 + case EV_UNUSED_INDEX94: // 94 + case EV_UNUSED_INDEX95: // 95 + break; + + // BFP - Blind + case EV_BLINDING: // 96 + if ( es->number == cg.snap->ps.clientNum + && ( !cg.blindLastAttackTime || cg.time - cg.blindLastAttackTime > 4000 ) ) { + cg.blind = qtrue; + cg.blindStartTime = cg.time; + cg.blindLastAttackTime = cg.time; + } + break; + + case EV_DEBUG_LINE: // 97 + CG_Beam( cent ); + break; + + case EV_STOPLOOPINGSOUND: // 98 trap_S_StopLoopingSound( es->number ); es->loopSound = 0; break; - case EV_DEBUG_LINE: - DEBUGNAME("EV_DEBUG_LINE"); - CG_Beam( cent ); + // BFP - Spark and beam struggle effect + case EV_SPARK: // 99 + ByteToDir( es->eventParm, dir ); + // BFP - Beam struggle effect + CG_BeamStruggleEffect( position, dir ); break; default: - DEBUGNAME("UNKNOWN"); CG_Error( "Unknown event: %i", event ); break; } - } @@ -1032,6 +1193,6 @@ void CG_CheckEvents( centity_t *cent ) { BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); CG_SetEntitySoundPosition( cent ); - CG_EntityEvent( cent, cent->lerpOrigin ); + CG_EntityEvent( cent, cent->lerpOrigin, -1 ); } diff --git a/source/cgame/cg_info.c b/source/cgame/cg_info.c index 1e1dd9c..7271326 100644 --- a/source/cgame/cg_info.c +++ b/source/cgame/cg_info.c @@ -114,10 +114,6 @@ void CG_LoadingClient( int clientNum ) { Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { - Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); - loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - } if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); @@ -127,8 +123,8 @@ void CG_LoadingClient( int clientNum ) { } } - Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); - Q_CleanStr( personality ); + BG_CleanName( Info_ValueForKey( info, "n" ), personality, sizeof( personality ), "unknown client" ); + BG_StripColor( personality ); if( cgs.gametype == GT_SINGLE_PLAYER ) { trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ), qtrue ); @@ -233,6 +229,22 @@ void CG_DrawInformation( void ) { y += PROP_HEIGHT; } + // BFP - Melee only + s = Info_ValueForKey( info, "g_meleeOnly" ); + if ( atoi( s ) > 0 ) { + UI_DrawProportionalString( 320, y, "MELEE ONLY", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // BFP - No flight + s = Info_ValueForKey( info, "g_noFlight" ); + if ( atoi( s ) > 0 ) { + UI_DrawProportionalString( 320, y, "NO FLIGHT", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + // game type switch ( cgs.gametype ) { case GT_FFA: @@ -244,9 +256,18 @@ void CG_DrawInformation( void ) { case GT_TOURNAMENT: s = "Tournament"; break; + case GT_SURVIVAL: // BFP - Survival + s = "Survival"; + break; + case GT_MONSTER: // BFP - Monster + s = "Monster"; + break; case GT_TEAM: s = "Team Deathmatch"; break; + case GT_TLMS: // BFP - Team Last Man Standing + s = "Team Last Man Standing"; + break; case GT_CTF: s = "Capture The Flag"; break; diff --git a/source/cgame/cg_local.h b/source/cgame/cg_local.h index d46ecab..6a488d2 100644 --- a/source/cgame/cg_local.h +++ b/source/cgame/cg_local.h @@ -79,12 +79,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define TEAM_OVERLAY_MAXNAME_WIDTH 12 #define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 -#define DEFAULT_MODEL "sarge" -#define DEFAULT_TEAM_MODEL "sarge" -#define DEFAULT_TEAM_HEAD "sarge" +#define DEFAULT_MODEL "bfp1-kyah" // BFP - Before Q3: "sarge" +#define DEFAULT_TEAM_MODEL "bfp1-kyah" // BFP - Before Q3: "sarge" +#define DEFAULT_TEAM_HEAD "bfp1-kyah" // BFP - Before Q3: "sarge" -#define DEFAULT_REDTEAM_NAME "Stroggs" -#define DEFAULT_BLUETEAM_NAME "Pagans" +#define DEFAULT_REDTEAM_NAME "Warriors" // BFP - Before Q3: "Stroggs" +#define DEFAULT_BLUETEAM_NAME "Fighters" // BFP - Before Q3: "Pagans" typedef enum { FOOTSTEP_NORMAL, @@ -139,16 +139,31 @@ typedef struct { lerpFrame_t legs, torso, flag; int painTime; int painDirection; // flip from 0 to 1 - int lightningFiring; + //int lightningFiring; + int kiTrailTime; // BFP - Ki trail time + int chargeSmokeTime; // BFP - Charge smoke time + int ultTierTransformTime; // BFP - Ultimate tier transformation time + + vec3_t muzzleOrigin; // BFP - Muzzle origin // railgun trail spawning vec3_t railgunImpact; qboolean railgunFlash; + // BFP - No machinegun spinning // machinegun spinning - float barrelAngle; - int barrelTime; - qboolean barrelSpinning; + //float barrelAngle; + //int barrelTime; + //qboolean barrelSpinning; + + int forceFieldStartTime; // BFP - Forcefield start time + + // BFP - TODO: Handle chargeAttack and chargeAutoFire for forcefield attack + qboolean chargeAutoFire; + + qboolean constantFireAtkPlayed; // BFP - To play constantFireAttack fire sound once + + qboolean lastChargeVoiceLevel; // BFP - To play charge voice once } playerEntity_t; //================================================= @@ -171,6 +186,9 @@ typedef struct centity_s { int dustTrailTime; int miscTime; + int delaySpawn; + qboolean delaySpawnPlayed; + int snapShotTime; // last time this entity was found in a snapshot playerEntity_t pe; @@ -210,9 +228,13 @@ typedef struct markPoly_s { typedef enum { LE_MARK, LE_EXPLOSION, + LE_EXPLOSION_SPHERE, // BFP - Explosion sphere + LE_EXPLOSION_RING, // BFP - Explosion ring + LE_EXPLOSION_SHELL, // BFP - Explosion shell LE_SPRITE_EXPLOSION, LE_FRAGMENT, LE_MOVE_SCALE_FADE, + LE_MOVE_DONT_SCALE_FADE, // BFP - Effect for explosion smoke LE_FALL_SCALE_FADE, LE_FADE_RGB, LE_SCALE_FADE, @@ -221,9 +243,7 @@ typedef enum { typedef enum { LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time - LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells - LEF_SOUND1 = 0x0004, // sound 1 for kamikaze - LEF_SOUND2 = 0x0008 // sound 2 for kamikaze + LEF_TUMBLE = 0x0002 // tumble over time, used for ejecting shells } leFlag_t; typedef enum { @@ -303,8 +323,8 @@ typedef struct { int botSkill; // 0 = not bot, 1-5 = bot - vec3_t color1; - vec3_t color2; + // BFP - No color1 + // vec3_t color1; int score; // updated by score servercmds int location; // location index for team mode @@ -315,9 +335,6 @@ typedef struct { int handicap; int wins, losses; // in tourney mode - int teamTask; // task in teamplay (offence/defence) - qboolean teamLeader; // true when this is a team leader - int powerups; // so can display quad/flag status int medkitUsageTime; @@ -333,8 +350,7 @@ typedef struct { char skinName[MAX_QPATH]; char headModelName[MAX_QPATH]; char headSkinName[MAX_QPATH]; - char redTeam[MAX_TEAMNAME]; - char blueTeam[MAX_TEAMNAME]; + qboolean deferred; qboolean newAnims; // true if using the new mission pack animations @@ -354,6 +370,14 @@ typedef struct { qhandle_t headModel; qhandle_t headSkin; + // BFP - Ultimate tier models and skins + qhandle_t ultTierLegsModel; + qhandle_t ultTierLegsSkin; + qhandle_t ultTierTorsoModel; + qhandle_t ultTierTorsoSkin; + qhandle_t ultTierHeadModel; + qhandle_t ultTierHeadSkin; + qhandle_t modelIcon; animation_t animations[MAX_TOTALANIMATIONS]; @@ -369,39 +393,59 @@ typedef struct weaponInfo_s { qboolean registered; gitem_t *item; - qhandle_t handsModel; // the hands don't actually draw, they just position the weapon - qhandle_t weaponModel; - qhandle_t barrelModel; + // BFP - No handsModel, weaponModel and barrelModel + //qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + //qhandle_t weaponModel; + //qhandle_t barrelModel; qhandle_t flashModel; vec3_t weaponMidpoint; // so it will rotate centered instead of by tag float flashDlight; vec3_t flashDlightColor; + // BFP - Indication here: flashSound sfxHandle_t flashSound[4]; // fast firing weapons randomly choose - qhandle_t weaponIcon; - qhandle_t ammoIcon; + sfxHandle_t attackChargeVoice; // BFP - attackChargeVoice + sfxHandle_t attackFireVoice; // BFP - attackFireVoice + + qhandle_t weaponIcon; // BFP - Indication here: attackIcon + // BFP - No ammoIcon + //qhandle_t ammoIcon; qhandle_t ammoModel; - qhandle_t missileModel; - sfxHandle_t missileSound; + qhandle_t missileModel; // BFP - Indication here: missileModel + qhandle_t missileShader; // BFP - For missileShader + int missileRotation; // BFP - For missileRotation + sfxHandle_t missileSound; // BFP - Indication here: missileSound void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); - float missileDlight; - vec3_t missileDlightColor; + int missileTrailFuncType; // BFP - Indication here: missileTrailFunc, for MISSILE_TRAIL_FUNC_* macro types + float missileDlight; // BFP - Indication here: missileDlight + vec3_t missileDlightColor; // BFP - Indication here: missileDlightColor int missileRenderfx; + qboolean constantFireAttack; // BFP - constantFireAttack + void (*ejectBrassFunc)( centity_t * ); - float trailRadius; - float wiTrailTime; + float trailRadius; // BFP - Indication here: missileTrailRadius + float wiTrailTime; // BFP - Indication here: missileTrailTime + + qboolean noExplosion; // BFP - noExplosion sfxHandle_t readySound; - sfxHandle_t firingSound; + sfxHandle_t firingSound; // BFP - Indication here: firingSound qboolean loopFireSound; + + sfxHandle_t chargeSound; // BFP - Charge sound } weaponInfo_t; +// BFP - missileTrailFunc types +#define MISSILE_TRAIL_FUNC_BEAM 1 // "beam" +#define MISSILE_TRAIL_FUNC_ROCKET 2 // "rocket" +#define MISSILE_TRAIL_FUNC_SPIRALBEAM 3 // "spiralbeam" +// "none" can be any value, anyway it's nothing // each IT_* item has an associated itemInfo_t // that constains media references necessary to present the @@ -418,14 +462,6 @@ typedef struct { } powerupInfo_t; -#define MAX_SKULLTRAIL 10 - -typedef struct { - vec3_t positions[MAX_SKULLTRAIL]; - int numpositions; -} skulltrail_t; - - #define MAX_REWARDSTACK 10 #define MAX_SOUNDBUFFER 20 @@ -535,9 +571,6 @@ typedef struct { int spectatorOffset; // current offset from start int spectatorPaintLen; // current offset from start - // skull trails - skulltrail_t skulltrails[MAX_CLIENTS]; - // centerprinting int centerPrintTime; int centerPrintCharWidth; @@ -627,6 +660,10 @@ typedef struct { //qboolean cameraMode; // if rendering from a loaded camera + // BFP - Blind + qboolean blind; + int blindStartTime; // when the current blind effect started + int blindLastAttackTime; // last time the player was 'blind attacked' // development tool refEntity_t testModelEntity; @@ -647,13 +684,8 @@ typedef struct { qhandle_t charsetPropB; qhandle_t whiteShader; - qhandle_t redCubeModel; - qhandle_t blueCubeModel; - qhandle_t redCubeIcon; - qhandle_t blueCubeIcon; qhandle_t redFlagModel; qhandle_t blueFlagModel; - qhandle_t neutralFlagModel; qhandle_t redFlagShader[3]; qhandle_t blueFlagShader[3]; qhandle_t flagShader[4]; @@ -663,11 +695,9 @@ typedef struct { qhandle_t redFlagFlapSkin; qhandle_t blueFlagFlapSkin; - qhandle_t neutralFlagFlapSkin; qhandle_t redFlagBaseModel; qhandle_t blueFlagBaseModel; - qhandle_t neutralFlagBaseModel; qhandle_t armorModel; qhandle_t armorIcon; @@ -690,9 +720,6 @@ typedef struct { qhandle_t smoke2; - qhandle_t machinegunBrassModel; - qhandle_t shotgunBrassModel; - qhandle_t railRingsShader; qhandle_t railCoreShader; @@ -707,10 +734,14 @@ typedef struct { qhandle_t viewBloodShader; qhandle_t tracerShader; qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t redCrosshairShader[NUM_CROSSHAIRS]; // BFP - BFP crosshair health feature qhandle_t lagometerShader; qhandle_t backTileShader; qhandle_t noammoShader; + // BFP - Ki attack charge up points + qhandle_t chargeupbuttgreen; + qhandle_t smokePuffShader; qhandle_t smokePuffRageProShader; qhandle_t shotgunSmokePuffShader; @@ -724,6 +755,12 @@ typedef struct { qhandle_t botSkillShaders[5]; + // BFP - HUD overlay + qhandle_t hudoverlay; + qhandle_t hudoverlayr; + + // BFP - NOTE: These mark shaders are unused, they didn't remove. Remove them in the future? + // wall mark shaders qhandle_t wakeMarkShader; qhandle_t bloodMarkShader; @@ -732,6 +769,9 @@ typedef struct { qhandle_t holeMarkShader; qhandle_t energyMarkShader; + // BFP - Crack mark shader + qhandle_t crackMarkShader; + // powerup shaders qhandle_t quadShader; qhandle_t redQuadShader; @@ -750,6 +790,18 @@ typedef struct { qhandle_t dishFlashModel; qhandle_t lightningExplosionModel; + // BFP - Explosion, projectile and beam models + qhandle_t nukeModel; + qhandle_t lowPolySphereModel; + qhandle_t highPolySphereModel; + qhandle_t beamModel; + + // BFP - Explosion shell shader + qhandle_t explosionShellShader; + + // BFP - Explosion shader tests + qhandle_t ImpactBeamExplosionShader; + // weapon effect shaders qhandle_t railExplosionShader; qhandle_t plasmaExplosionShader; @@ -763,13 +815,48 @@ typedef struct { qhandle_t teleportEffectModel; qhandle_t teleportEffectShader; - qhandle_t invulnerabilityPowerupModel; + // BFP - No invulnerability powerup +// qhandle_t invulnerabilityPowerupModel; - qhandle_t auraModel; // BFP - Aura model + // BFP - Particle shaders + qhandle_t pebbleShader1; + qhandle_t pebbleShader2; + qhandle_t pebbleShader3; + qhandle_t particleSmokeShader; + qhandle_t sparkShader1; + qhandle_t sparkShader2; + + // BFP - Optional 3D pebble/rock models + qhandle_t pebbleMdl1; + qhandle_t pebbleMdl2; + qhandle_t pebbleMdl3; + + // BFP - Aura models + qhandle_t auraModel; // BFP - Normal aura model qhandle_t backauraModel; // BFP - Back aura model qhandle_t flyauraModel; // BFP - Fly aura model qhandle_t runauraModel; // BFP - Run aura model - qhandle_t auraEffectShader; // BFP - Aura shader + // BFP - Aura shaders + qhandle_t auraRedTinyShader; + qhandle_t auraRedChargeShader; + qhandle_t auraRedUseShader; + qhandle_t auraBlueTinyShader; + qhandle_t auraBlueChargeShader; + qhandle_t auraBlueUseShader; + qhandle_t auraYellowTinyShader; + qhandle_t auraYellowChargeShader; + qhandle_t auraYellowUseShader; + qhandle_t ultimateAuraShader; + + // BFP - Ki trail shaders + qhandle_t kiTrailRedShader; + qhandle_t kiTrailBlueShader; + qhandle_t kiTrailYellowShader; + + // BFP - Ki attack beam and projectile shaders (for testing purposes) + qhandle_t SSBSpiralShader; + qhandle_t SSBBeamShader; + qhandle_t PowerWaveBeamShader; // scoreboard headers qhandle_t scoreboardName; @@ -778,12 +865,31 @@ typedef struct { qhandle_t scoreboardTime; // medals shown during gameplay - qhandle_t medalImpressive; qhandle_t medalExcellent; + // BFP - No impressive, gauntlet, defend, assist and cap medals +/* + qhandle_t medalImpressive; qhandle_t medalGauntlet; qhandle_t medalDefend; qhandle_t medalAssist; qhandle_t medalCapture; +*/ + + // BFP - Monster models, animations, skins and icon + qhandle_t monsterLegsModel; + qhandle_t monsterLegsSkin; + qhandle_t monsterTorsoModel; + qhandle_t monsterTorsoSkin; + qhandle_t monsterHeadModel; + qhandle_t monsterHeadSkin; + qhandle_t monsterUltTierLegsModel; + qhandle_t monsterUltTierLegsSkin; + qhandle_t monsterUltTierTorsoModel; + qhandle_t monsterUltTierTorsoSkin; + qhandle_t monsterUltTierHeadModel; + qhandle_t monsterUltTierHeadSkin; + qhandle_t monsterModelIcon; + animation_t monsterAnimations[MAX_TOTALANIMATIONS]; // sounds sfxHandle_t quadSound; @@ -827,15 +933,19 @@ typedef struct { sfxHandle_t hitSoundHighArmor; sfxHandle_t hitSoundLowArmor; sfxHandle_t hitTeamSound; - sfxHandle_t impressiveSound; sfxHandle_t excellentSound; + sfxHandle_t firstExcellentSound; + + // BFP - No impressive, gauntlet, defend and assist sounds +/* + sfxHandle_t impressiveSound; sfxHandle_t deniedSound; sfxHandle_t humiliationSound; sfxHandle_t assistSound; sfxHandle_t defendSound; sfxHandle_t firstImpressiveSound; - sfxHandle_t firstExcellentSound; sfxHandle_t firstHumiliationSound; +*/ sfxHandle_t takenLeadSound; sfxHandle_t tiedLeadSound; @@ -849,7 +959,8 @@ typedef struct { sfxHandle_t watrOutSound; sfxHandle_t watrUnSound; - sfxHandle_t flightSound; + // BFP - No flight powerup sound +// sfxHandle_t flightSound; sfxHandle_t medkitSound; sfxHandle_t weaponHoverSound; @@ -871,13 +982,11 @@ typedef struct { sfxHandle_t redFlagReturnedSound; sfxHandle_t blueFlagReturnedSound; - sfxHandle_t neutralFlagReturnedSound; sfxHandle_t enemyTookYourFlagSound; sfxHandle_t enemyTookTheFlagSound; sfxHandle_t yourTeamTookEnemyFlagSound; sfxHandle_t yourTeamTookTheFlagSound; sfxHandle_t youHaveFlagSound; - sfxHandle_t yourBaseIsUnderAttackSound; sfxHandle_t holyShitSound; // tournament sounds @@ -891,7 +1000,8 @@ typedef struct { qhandle_t selectCursor; qhandle_t sizeCursor; - sfxHandle_t regenSound; + // BFP - No regen sound +// sfxHandle_t regenSound; sfxHandle_t protectSound; sfxHandle_t n_healthSound; sfxHandle_t hgrenb1aSound; @@ -901,9 +1011,24 @@ typedef struct { sfxHandle_t wstbimpdSound; sfxHandle_t wstbactvSound; + // BFP - Tier up sound + sfxHandle_t tierUpSound; + + // BFP - Explosion sounds + sfxHandle_t explosion1Sound; + sfxHandle_t explosion2Sound; + sfxHandle_t explosion3Sound; + sfxHandle_t explosion4Sound; + sfxHandle_t explosion5Sound; + sfxHandle_t explosion6Sound; + sfxHandle_t kiChargeSound; // BFP - Ki charge sound sfxHandle_t kiUseSound; // BFP - Ki use sound + sfxHandle_t defaultKiBeamExplosionWaveSound; // BFP - Default ki beam and ki explosion wave sound + sfxHandle_t defaultKiFiringAttackSound; // BFP - Default ki firing sound + sfxHandle_t defaultKiChargingSound; // BFP - Default ki attack charging sound + sfxHandle_t diskKiChargingSound; // BFP - Disk ki attack charging sound } cgMedia_t; @@ -917,6 +1042,7 @@ typedef struct { float screenXScale; // derived from glconfig float screenYScale; float screenXBias; + float screenYBias; int serverCommandSequence; // reliable command stream counter int processedSnapshotNum;// the number of snapshots cgame has requested @@ -931,6 +1057,8 @@ typedef struct { int capturelimit; int timelimit; int maxclients; + // BFP - Monster gamemode, to get g_monster g_cvar + int monster; char mapname[MAX_QPATH]; char redTeam[MAX_QPATH]; char blueTeam[MAX_QPATH]; @@ -1010,6 +1138,8 @@ extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; #include "cg_cvar.h" #undef EXTERN_CG_CVAR +extern const char *eventnames[EV_MAX]; + // // cg_main.c // @@ -1031,8 +1161,8 @@ void CG_MouseEvent(int x, int y); void CG_EventHandling(int type); void CG_RankRunFrame( void ); void CG_SetScoreSelection(void *menu); -score_t *CG_GetSelectedScore(); -void CG_BuildSpectatorString(); +score_t *CG_GetSelectedScore( void ); +void CG_BuildSpectatorString( void ); // @@ -1060,8 +1190,7 @@ void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); void CG_FillRect( float x, float y, float width, float height, const float *color ); void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); void CG_DrawString( float x, float y, const char *string, - float charWidth, float charHeight, const float *modulate ); - + const vec4_t setColor, float charWidth, float charHeight, int maxChars, int flags ); void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); @@ -1073,7 +1202,7 @@ void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); int CG_DrawStrlen( const char *str ); float *CG_FadeColor( int startMsec, int totalMsec ); -float *CG_TeamColor( int team ); +const float *CG_TeamColor( team_t team ); void CG_TileClear( void ); void CG_ColorForHealth( vec4_t hcolor ); void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); @@ -1083,6 +1212,21 @@ void CG_DrawRect( float x, float y, float width, float height, float size, const 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); +#define USE_NEW_FONT_RENDERER + +// flags for CG_DrawString +enum { + DS_SHADOW = 0x1, + DS_FORCE_COLOR = 0x2, + DS_PROPORTIONAL = 0x4, + DS_CENTER = 0x8, // alignment + DS_RIGHT = 0x10 // alignment +}; +#ifdef USE_NEW_FONT_RENDERER +void CG_LoadFonts( void ); +void CG_SelectFont( int index ); +#endif + // // cg_draw.c, cg_newDraw.c @@ -1105,23 +1249,23 @@ void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y 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(); +void CG_SelectPrevPlayer( void ); +void CG_SelectNextPlayer( void ); float CG_GetValue(int ownerDraw); qboolean CG_OwnerDrawVisible(int flags); void CG_RunMenuScript(char **args); -void CG_ShowResponseHead(); +void CG_ShowResponseHead( void ); void CG_SetPrintString(int type, const char *p); -void CG_InitTeamChat(); +void CG_InitTeamChat( void ); void CG_GetTeamColor(vec4_t *color); -const char *CG_GetGameStatusText(); -const char *CG_GetKillerText(); +const char *CG_GetGameStatusText( void ); +const char *CG_GetKillerText( void ); 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(); +void CG_CheckOrderPending( void ); +const char *CG_GameTypeString( void ); +qboolean CG_YourTeamHasFlag( void ); +qboolean CG_OtherTeamHasFlag( void ); qhandle_t CG_StatusHandle(int task); @@ -1129,13 +1273,16 @@ qhandle_t CG_StatusHandle(int task); // // cg_player.c // +// BFP - Monster gamemode, those 2 functions are used for g_monster to load monster model +qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ); +qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ); + +void CG_ModelSize( refEntity_t *model, float size ); // BFP - Model size void CG_Player( centity_t *cent ); void CG_ResetPlayerEntity( centity_t *cent ); -void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); +void CG_AddRefEntityWithPowerups( refEntity_t re, entityState_t *state, int team ); void CG_NewClientInfo( int clientNum ); sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); -qboolean CG_GetTagOrientationFromPlayerEntityParentModel( centity_t *cent, refEntity_t *parent, - qhandle_t parentModel, char *tagName, orientation_t *tagOrient ); // BFP - Parent model tag orientation, used for first person vis mode // // cg_predict.c @@ -1153,7 +1300,7 @@ void CG_LoadDeferredPlayers( void ); // void CG_CheckEvents( centity_t *cent ); const char *CG_PlaceString( int rank ); -void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ); void CG_PainEvent( centity_t *cent, int health ); @@ -1190,8 +1337,9 @@ void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean fles void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end ); void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); +void CG_BFPBeamTrail( centity_t *ent, const weaponInfo_t *wi ); // BFP - BFP Beam trail handling void CG_AddViewWeapon (playerState_t *ps); -void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team, char *tagName ); void CG_DrawWeaponSelect( void ); void CG_OutOfAmmoChange( void ); // should this be in pmove? @@ -1228,7 +1376,7 @@ localEntity_t *CG_SmokePuff( const vec3_t p, int leFlags, qhandle_t hShader ); void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); -void CG_SpawnEffect( vec3_t org ); +refEntity_t *CG_SpawnEffect( const vec3_t origin, qboolean firstPerson ); void CG_ScorePlum( int client, vec3_t org, int score ); void CG_GibPlayer( vec3_t playerOrigin ); @@ -1239,6 +1387,38 @@ 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 ); +// BFP - Explosion models +localEntity_t *CG_SpawnExplosionModel( vec3_t origin, vec3_t dir, leType_t type, qhandle_t hModel, qhandle_t shader, float duration ); +// BFP - Debris particles explosion +void CG_DebrisExplosion( vec3_t origin, vec3_t dir ); +// BFP - Spark particles explosion +void CG_SparksExplosion( vec3_t origin, vec3_t dir ); +// BFP - Beam struggle sparks +void CG_BeamStruggleEffect( vec3_t origin, vec3_t dir ); +// BFP - Explosion smoke +void CG_SmokeExplosion( vec3_t origin, vec3_t dir ); +// BFP - Explosion sounds +void CG_ExplosionSound( vec3_t origin ); +// BFP - Explosion effects +void CG_ExplosionEffect( vec3_t origin, vec3_t dir ); +// BFP - Forcefield effect +void CG_ForceFieldEffect( centity_t *cent, vec3_t origin, char *explosionModel, char *explosionShader ); + +// BFP - Trails +// +// cg_trails.c +// +/* Trail types */ +#define KI_TRAIL 0 +#define BEAM_TRAIL 1 +#define MISSILE_TRAIL 2 +void CG_InitTrails( void ); +void CG_ResetTrail( const int TRAIL_TYPE, int entityNum, vec3_t origin ); +void CG_KiTrail( int entityNum, vec3_t origin, qboolean remove, qhandle_t hShader ); +void CG_BeamTrail( int entityNum, vec3_t origin, vec3_t muzzleOrigin, qhandle_t hShader ); +void CG_CorkscrewTrail( int entityNum, vec3_t origin, vec3_t muzzleOrigin, qhandle_t beamShader, qhandle_t corkscrewShader ); +void CG_MissileTrail( int entityNum, vec3_t origin, qhandle_t hShader, vec3_t color, qboolean rainbow ); +void CG_DrawMissileTrails( void ); // // cg_snapshot.c @@ -1318,7 +1498,7 @@ int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); void trap_FS_Read( void *buffer, int len, fileHandle_t f ); void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); void trap_FS_FCloseFile( fileHandle_t f ); -int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t +int trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // add commands to the local console as if they were typed in // for map changing, etc. The command is not executed immediately, @@ -1476,16 +1656,23 @@ qboolean trap_GetEntityToken( char *buffer, int bufferSize ); void CG_ClearParticles (void); void CG_AddParticles (void); -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); -void CG_AddParticleShrapnel (localEntity_t *le); -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); -extern qboolean initparticles; -int CG_NewParticleArea ( int num ); - - +// BFP - Bubble particle +void CG_ParticleBubble (centity_t *cent, qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t origin2, int turbtime, float range, float size); +// BFP - Dash smoke particle for ki boost when moving in the ground +void CG_ParticleDashSmoke (centity_t *cent, qhandle_t pshader, vec3_t origin, float size, float velocityDisp, float upVelocity, float accel); +// BFP - Antigrav rock particles for charging +void CG_ParticleAntigravRock (qhandle_t pshader, qhandle_t pmodel, centity_t *cent, int entityNum, vec3_t origin, float size, float spawnRange, float endTime); +void CG_AntigravRockHandling (centity_t *cent); +// BFP - Particle aura +void CG_ParticleAura (centity_t *cent, int entityNum, qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t origin2, float range); +void CG_ParticleAuraHandling (centity_t *cent); +// BFP - Bouncing debris rock fragment (explosion) +void CG_ParticleRockDebris (qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t vel, float size, float velocity, float accel); +// BFP - Water entry splash +void CG_ParticleWaterSplash (qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t vel, float size, float velocity, float accel); +// BFP - Spark particle +void CG_ParticleSparks (qhandle_t pshader, vec3_t origin, vec3_t vel); +// BFP - Charge smoke particle for ki charge +void CG_ParticleChargeSmoke (centity_t *cent, qhandle_t pshader, vec3_t origin, float size, float radialVel, float baseRadius); +// BFP - Beam struggle spark particle +void CG_ParticleBeamStruggleSpark (qhandle_t pshader, vec3_t origin, vec3_t vel); diff --git a/source/cgame/cg_localents.c b/source/cgame/cg_localents.c index 53ec373..3c18b72 100644 --- a/source/cgame/cg_localents.c +++ b/source/cgame/cg_localents.c @@ -389,6 +389,47 @@ static void CG_AddMoveScaleFade( localEntity_t *le ) { } +/* +================== +CG_AddMoveDontScaleFade +================== +*/ +static void CG_AddMoveDontScaleFade( localEntity_t *le ) { // BFP - For LE_MOVE_DONT_SCALE_FADE type + refEntity_t *re; + float c; + + re = &le->refEntity; + + // fade / grow time + c = (float)( le->endTime - cg.time ) * (float)le->lifeRate; + + // instead 0xff (255), use 510 to make the shader more visible + re->shaderRGBA[3] = 510 * c * le->color[3]; + if ( re->shaderRGBA[3] <= 0 ) { // no alpha, disappear then + CG_FreeLocalEntity( le ); + return; + } + + // slow down the movement during 0.33 sec + if ( cg.time - le->startTime < 330 ) { + le->pos.trDelta[0] *= 0.998; + le->pos.trDelta[1] *= 0.998; + } else { // stop moving horizontally + vec3_t currentPos; + BG_EvaluateTrajectory( &le->pos, cg.time, currentPos ); + VectorCopy( currentPos, le->pos.trBase ); + le->pos.trTime = cg.time; + le->pos.trDelta[0] = le->pos.trDelta[1] = 0; + } + + re->radius = le->radius; + + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + trap_R_AddRefEntityToScene( re ); +} + + /* =================== CG_AddScaleFade @@ -476,11 +517,37 @@ static void CG_AddExplosion( localEntity_t *ex ) { ent = &ex->refEntity; + if ( cg_bigExplosions.integer > 0 ) { // BFP - Big explosions + float scale = 1.04f; + float endTime = (float)( ex->endTime ) - (float)( ex->lifeRate * 0.5 ); + float endScale = (float)( ex->endTime - cg.time ) / (float)( ex->endTime - ex->startTime ); + + switch( ex->leType ) { + case LE_EXPLOSION: + scale = 1.05f; // scale a bit faster to adjust + break; + case LE_EXPLOSION_SPHERE: + if ( cg.time > endTime ) { + scale = 0.9f * ( endScale + 0.5f ); // decrease size + } + break; + case LE_EXPLOSION_SHELL: + scale = 1.12f; // scale faster than the explosion sphere + break; + default: + break; + } + + VectorScale( ent->axis[0], scale, ent->axis[0] ); + VectorScale( ent->axis[1], scale, ent->axis[1] ); + VectorScale( ent->axis[2], scale, ent->axis[2] ); + } + // add the entity trap_R_AddRefEntityToScene(ent); // add the dlight - if ( ex->light ) { + if ( cg_lightExplosions.integer > 0 ) { // BFP - Dynamic explosion lights float light; light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); @@ -661,6 +728,9 @@ void CG_AddLocalEntities( void ) { break; case LE_EXPLOSION: + case LE_EXPLOSION_SPHERE: // BFP - Explosion sphere + case LE_EXPLOSION_RING: // BFP - Explosion ring + case LE_EXPLOSION_SHELL: // BFP - Explosion shell CG_AddExplosion( le ); break; @@ -672,6 +742,10 @@ void CG_AddLocalEntities( void ) { CG_AddMoveScaleFade( le ); break; + case LE_MOVE_DONT_SCALE_FADE: // BFP - Effect for explosion smoke + CG_AddMoveDontScaleFade( le ); + break; + case LE_FADE_RGB: // teleporters, railtrails CG_AddFadeRGB( le ); break; diff --git a/source/cgame/cg_main.c b/source/cgame/cg_main.c index 9b13581..bd066e3 100644 --- a/source/cgame/cg_main.c +++ b/source/cgame/cg_main.c @@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // cg_main.c -- initialization and primary entry point for cgame #include "cg_local.h" -int forceModelModificationCount = -1; +// int forceModelModificationCount = -1; void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); void CG_Shutdown( void ); @@ -37,7 +37,7 @@ This is the only way control passes into the module. This must be the very first function compiled into the .q3vm file ================ */ -intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { +DLLEXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { switch ( command ) { case CG_INIT: @@ -119,15 +119,17 @@ void CG_RegisterCvars( void ) { trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); cgs.localServer = atoi( var ); - forceModelModificationCount = cg_forceModel.modificationCount; + // forceModelModificationCount = cg_forceModel.modificationCount; trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); trap_Cvar_Register(NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - //trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - //trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); } -/* +// BFP - No force model (In the future, remove cg_forceModel, which wasn't removed originally?) +#if 0 +/* =================== CG_ForceModelChange =================== @@ -145,6 +147,7 @@ static void CG_ForceModelChange( void ) { CG_NewClientInfo( i ); } } +#endif /* ================= @@ -175,11 +178,14 @@ void CG_UpdateCvars( void ) { trap_Cvar_Set( "teamoverlay", "1" ); } + // BFP - No force model (In the future, remove cg_forceModel, which wasn't removed originally?) +#if 0 // if force model changed if ( forceModelModificationCount != cg_forceModel.modificationCount ) { forceModelModificationCount = cg_forceModel.modificationCount; CG_ForceModelChange(); } +#endif } int CG_CrosshairPlayer( void ) { @@ -201,7 +207,7 @@ void QDECL CG_Printf( const char *msg, ... ) { char text[1024]; va_start (argptr, msg); - vsprintf (text, msg, argptr); + Q_vsprintf (text, msg, argptr); va_end (argptr); trap_Print( text ); @@ -212,7 +218,7 @@ void QDECL CG_Error( const char *msg, ... ) { char text[1024]; va_start (argptr, msg); - vsprintf (text, msg, argptr); + Q_vsprintf (text, msg, argptr); va_end (argptr); trap_Error( text ); @@ -226,7 +232,7 @@ void QDECL Com_Error( int level, const char *error, ... ) { char text[1024]; va_start (argptr, error); - vsprintf (text, error, argptr); + Q_vsprintf (text, error, argptr); va_end (argptr); CG_Error( "%s", text); @@ -237,7 +243,7 @@ void QDECL Com_Printf( const char *msg, ... ) { char text[1024]; va_start (argptr, msg); - vsprintf (text, msg, argptr); + Q_vsprintf (text, msg, argptr); va_end (argptr); CG_Printf ("%s", text); @@ -364,7 +370,6 @@ static void CG_RegisterSounds( void ) { cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); - cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); } @@ -389,12 +394,15 @@ static void CG_RegisterSounds( void ) { cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav", qfalse ); - cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); cgs.media.excellentSound = trap_S_RegisterSound( "sound/feedback/excellent.wav", qtrue ); + // BFP - No impressive, gauntlet, defend and assist sounds +#if 0 + cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); cgs.media.deniedSound = trap_S_RegisterSound( "sound/feedback/denied.wav", qtrue ); cgs.media.humiliationSound = trap_S_RegisterSound( "sound/feedback/humiliation.wav", qtrue ); cgs.media.assistSound = trap_S_RegisterSound( "sound/feedback/assist.wav", qtrue ); cgs.media.defendSound = trap_S_RegisterSound( "sound/feedback/defense.wav", qtrue ); +#endif cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/feedback/takenlead.wav", qtrue); cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/feedback/tiedlead.wav", qtrue); @@ -450,7 +458,8 @@ static void CG_RegisterSounds( void ) { } // FIXME: only needed with item - cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); + // BFP - No flight powerup sound + // cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav", qfalse); cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav", qfalse); cgs.media.sfx_ric1 = trap_S_RegisterSound ("sound/weapons/machinegun/ric1.wav", qfalse); @@ -460,19 +469,131 @@ static void CG_RegisterSounds( void ) { cgs.media.sfx_rockexp = trap_S_RegisterSound ("sound/weapons/rocket/rocklx1a.wav", qfalse); cgs.media.sfx_plasmaexp = trap_S_RegisterSound ("sound/weapons/plasma/plasmx1a.wav", qfalse); - cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); + // BFP - No regen sound + // cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); cgs.media.protectSound = trap_S_RegisterSound("sound/items/protect3.wav", qfalse); cgs.media.n_healthSound = trap_S_RegisterSound("sound/items/n_health.wav", qfalse ); cgs.media.hgrenb1aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb1a.wav", qfalse); cgs.media.hgrenb2aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb2a.wav", qfalse); + // BFP - Explosion sounds + cgs.media.explosion1Sound = trap_S_RegisterSound("sound/bfp/explosion1.wav", qfalse); + cgs.media.explosion2Sound = trap_S_RegisterSound("sound/bfp/explosion2.wav", qfalse); + cgs.media.explosion3Sound = trap_S_RegisterSound("sound/bfp/explosion3.wav", qfalse); + cgs.media.explosion4Sound = trap_S_RegisterSound("sound/bfp/explosion4.wav", qfalse); + cgs.media.explosion5Sound = trap_S_RegisterSound("sound/bfp/explosion5.wav", qfalse); + cgs.media.explosion6Sound = trap_S_RegisterSound("sound/bfp/explosion6.wav", qfalse); + cgs.media.kiChargeSound = trap_S_RegisterSound("sound/bfp/kicharge1.wav", qfalse); // BFP - Ki charge sound cgs.media.kiUseSound = trap_S_RegisterSound("sound/bfp/kiuse1.wav", qfalse); // BFP - Ki use sound + cgs.media.defaultKiChargingSound = trap_S_RegisterSound("sound/bfp/attackcharge1.wav", qfalse); // BFP - Default ki charging attack sound + cgs.media.diskKiChargingSound = trap_S_RegisterSound("sound/bfp/diskcharge1.wav", qfalse); // BFP - Disk ki charging attack sound + cgs.media.tierUpSound = trap_S_RegisterSound("sound/bfp/tierup.wav", qfalse); // BFP - Tier up sound + + // BFP - NOTE: Sounds like that are reusing Q3 ones + cgs.media.defaultKiBeamExplosionWaveSound = trap_S_RegisterSound("sound/weapons/rocket/rockfly.wav", qfalse); // BFP - Default ki beam and ki explosion wave sound + cgs.media.defaultKiFiringAttackSound = trap_S_RegisterSound("sound/weapons/bfg/bfg_fire.wav", qfalse); // BFP - Default ki firing sound } //=================================================================================== +/* +========================== +CG_RegisterMonsterModel +========================== +*/ +static qboolean CG_RegisterMonsterModel( void ) { // BFP - Monster gamemode, register monster model + char filename[MAX_QPATH*2]; + + // legs + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", MONSTER_NAME ); + cgs.media.monsterLegsModel = trap_R_RegisterModel( filename ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjlegs.md3", MONSTER_NAME ); + cgs.media.monsterUltTierLegsModel = trap_R_RegisterModel( filename ); + + // torso + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", MONSTER_NAME ); + cgs.media.monsterTorsoModel = trap_R_RegisterModel( filename ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjtorso.md3", MONSTER_NAME ); + cgs.media.monsterUltTierTorsoModel = trap_R_RegisterModel( filename ); + + // head + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", MONSTER_NAME ); + cgs.media.monsterHeadModel = trap_R_RegisterModel( filename ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjhead.md3", MONSTER_NAME ); + cgs.media.monsterUltTierHeadModel = trap_R_RegisterModel( filename ); + + if ( !cgs.media.monsterLegsModel || !cgs.media.monsterTorsoModel || !cgs.media.monsterHeadModel ) { + return qfalse; + } + return qtrue; +} + +/* +========================== +CG_RegisterMonsterSkin +========================== +*/ +static void CG_RegisterMonsterSkin( void ) { // BFP - Monster gamemode, register monster skin + char filename[MAX_QPATH]; + + // legs + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", MONSTER_NAME, "default" ); + cgs.media.monsterLegsSkin = trap_R_RegisterSkin( filename ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjlower.skin", MONSTER_NAME, "default" ); + cgs.media.monsterUltTierLegsSkin = trap_R_RegisterSkin( filename ); + + // torso + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", MONSTER_NAME, "default" ); + cgs.media.monsterTorsoSkin = trap_R_RegisterSkin( filename ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjtorso.skin", MONSTER_NAME, "default" ); + cgs.media.monsterUltTierTorsoSkin = trap_R_RegisterSkin( filename ); + + // head + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", MONSTER_NAME, "default" ); + cgs.media.monsterHeadSkin = trap_R_RegisterSkin( filename ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjhead.skin", MONSTER_NAME, "default" ); + cgs.media.monsterUltTierHeadSkin = trap_R_RegisterSkin( filename ); +} + +/* +========================== +CG_RegisterMonsterAnimations +========================== +*/ +static void CG_RegisterMonsterAnimations( void ) { // BFP - Monster gamemode, register monster skin + char filename[MAX_QPATH*2]; + clientInfo_t temp; + + memset( &temp, 0, sizeof(temp) ); + + Com_sprintf( filename, sizeof(filename), "models/players/%s/animation.cfg", MONSTER_NAME ); + if ( CG_ParseAnimationFile( filename, &temp ) ) { + memcpy( cgs.media.monsterAnimations, temp.animations, sizeof(cgs.media.monsterAnimations) ); + } +} + +/* +========================== +CG_RegisterMonsterModelIcon +========================== +*/ +static void CG_RegisterMonsterModelIcon( void ) { // BFP - Monster gamemode, register monster model icon + char filename[MAX_QPATH*2]; + clientInfo_t temp; + + memset( &temp, 0, sizeof(temp) ); + + if ( CG_FindClientHeadFile( filename, sizeof(filename), &temp, "default", MONSTER_NAME, "default", "icon", "skin" ) ) { + cgs.media.monsterModelIcon = trap_R_RegisterShaderNoMip( filename ); + } else if ( CG_FindClientHeadFile( filename, sizeof(filename), &temp, "default", MONSTER_NAME, "default", "icon", "tga" ) ) { + cgs.media.monsterModelIcon = trap_R_RegisterShaderNoMip( filename ); + } + if ( !cgs.media.monsterModelIcon ) { + cgs.media.monsterModelIcon = cgs.media.deferShader; + } +} /* ================= @@ -543,11 +664,22 @@ static void CG_RegisterGraphics( void ) { for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) ); + cgs.media.redCrosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/redcrosshair%c", 'a'+i) ); // BFP - BFP crosshair health feature } cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); + // BFP - NOTE: Shaders like that on the HUD must be trap_R_RegisterShaderNoMip, + // otherwise appears weird lines around the image/texture + + // BFP - Ki attack charge up points + cgs.media.chargeupbuttgreen = trap_R_RegisterShaderNoMip( "menu/art/chargeupbuttgreen" ); + + // BFP - HUD overlay + cgs.media.hudoverlay = trap_R_RegisterShaderNoMip( "menu/art/hudoverlay" ); + cgs.media.hudoverlayr = trap_R_RegisterShaderNoMip( "menu/art/hudoverlayr" ); + // powerup shaders cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); @@ -557,13 +689,6 @@ static void CG_RegisterGraphics( void ) { cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { - cgs.media.redCubeModel = trap_R_RegisterModel( "models/powerups/orb/r_orb.md3" ); - cgs.media.blueCubeModel = trap_R_RegisterModel( "models/powerups/orb/b_orb.md3" ); - cgs.media.redCubeIcon = trap_R_RegisterShader( "icons/skull_red" ); - cgs.media.blueCubeIcon = trap_R_RegisterShader( "icons/skull_blue" ); - } - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); @@ -575,7 +700,8 @@ static void CG_RegisterGraphics( void ) { cgs.media.blueFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu3" ); } - if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer + || cgs.gametype == GT_SURVIVAL ) { // BFP - Survival gametype, needs to draw the line status too 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" ); @@ -584,9 +710,6 @@ static void CG_RegisterGraphics( void ) { cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); cgs.media.armorIcon = trap_R_RegisterShaderNoMip( "icons/iconr_yellow" ); - cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); - cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); - cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); @@ -607,22 +730,62 @@ static void CG_RegisterGraphics( void ) { 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"); + // BFP - Explosion, projectile and beam models + cgs.media.nukeModel = trap_R_RegisterModel( "models/weaphits/nuke.md3" ); + cgs.media.lowPolySphereModel = trap_R_RegisterModel( "models/weaphits/sphere.md3" ); + cgs.media.highPolySphereModel = trap_R_RegisterModel( "models/weaphits/sphere_hi.md3" ); + cgs.media.beamModel = trap_R_RegisterModel( "models/weaphits/beam.md3" ); + + // BFP - Explosion shell shader + cgs.media.explosionShellShader = trap_R_RegisterShader( "explosionShellShader" ); + // BFP - Explosion ring shader (reused rail gun explosion) + cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); + + // BFP - Explosion shader test + cgs.media.ImpactBeamExplosionShader = trap_R_RegisterShader( "ImpactBeamExplosionShader" ); + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); - cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); - cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); + // BFP - No invulnerability powerup + // cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); + // BFP - No impressive, gauntlet, defend, assist and cap medals +#if 0 + cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); +#endif - cgs.media.auraModel = trap_R_RegisterModel( "models/effects/aura.md3" ); // BFP - Aura model + // BFP - Aura models + cgs.media.auraModel = trap_R_RegisterModel( "models/effects/aura.md3" ); // BFP - Normal aura model cgs.media.backauraModel = trap_R_RegisterModel( "models/effects/backaura.md3" ); // BFP - Back aura model cgs.media.flyauraModel = trap_R_RegisterModel( "models/effects/flyaura.md3" ); // BFP - Fly aura model cgs.media.runauraModel = trap_R_RegisterModel( "models/effects/runaura.md3" ); // BFP - Run aura model - cgs.media.auraEffectShader = trap_R_RegisterShader( "powerups/redtiny" ); // BFP - Aura shader + + // BFP - Aura shaders + cgs.media.auraBlueTinyShader = trap_R_RegisterShader( "powerups/bluetiny" ); + cgs.media.auraBlueChargeShader = trap_R_RegisterShader( "powerups/bluecharge" ); + cgs.media.auraBlueUseShader = trap_R_RegisterShader( "powerups/blueuse" ); + cgs.media.auraRedTinyShader = trap_R_RegisterShader( "powerups/redtiny" ); + cgs.media.auraRedChargeShader = trap_R_RegisterShader( "powerups/redcharge" ); + cgs.media.auraRedUseShader = trap_R_RegisterShader( "powerups/reduse" ); + cgs.media.auraYellowTinyShader = trap_R_RegisterShader( "powerups/yellowtiny" ); + cgs.media.auraYellowChargeShader = trap_R_RegisterShader( "powerups/yellowcharge" ); + cgs.media.auraYellowUseShader = trap_R_RegisterShader( "powerups/yellowuse" ); + cgs.media.ultimateAuraShader = trap_R_RegisterShader( "powerups/ultimateaura" ); + + // BFP - Ki trail shaders + cgs.media.kiTrailBlueShader = trap_R_RegisterShader( "powerups/bluekitrail" ); + cgs.media.kiTrailRedShader = trap_R_RegisterShader( "powerups/redkitrail" ); + cgs.media.kiTrailYellowShader = trap_R_RegisterShader( "powerups/yellowkitrail" ); + + // BFP - Ki attack beam and projectile shaders (for testing purposes) + cgs.media.PowerWaveBeamShader = trap_R_RegisterShader( "PowerWaveBeamShader" ); + cgs.media.SSBSpiralShader = trap_R_RegisterShader( "SSBSpiralShader" ); + cgs.media.SSBBeamShader = trap_R_RegisterShader( "SSBBeamShader" ); memset( cg_items, 0, sizeof( cg_items ) ); memset( cg_weapons, 0, sizeof( cg_weapons ) ); @@ -646,6 +809,28 @@ static void CG_RegisterGraphics( void ) { cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); + // BFP - Crack mark shader + cgs.media.crackMarkShader = trap_R_RegisterShader( "crackMarkShader" ); + + // BFP - Particle shaders + cgs.media.pebbleShader1 = trap_R_RegisterShader( "pebbleShader1" ); // BFP - Rock particles for charging and explosion + cgs.media.pebbleShader2 = trap_R_RegisterShader( "pebbleShader2" ); // BFP - Rock particles for charging and explosion + cgs.media.pebbleShader3 = trap_R_RegisterShader( "pebbleShader3" ); // BFP - Rock particles for charging and explosion + cgs.media.particleSmokeShader = trap_R_RegisterShader( "particleSmokeShader" ); // BFP - Smoke particle when using ki boost in the ground and for the explosion + cgs.media.sparkShader1 = trap_R_RegisterShader( "sparkShader1" ); // BFP - Spark particle for explosion + cgs.media.sparkShader2 = trap_R_RegisterShader( "sparkShader2" ); // BFP - Spark particle for explosion + + // BFP - Optional 3D pebble/rock models + cgs.media.pebbleMdl1 = trap_R_RegisterModel( "models/effects/pebble1.md3" ); + cgs.media.pebbleMdl2 = trap_R_RegisterModel( "models/effects/pebble2.md3" ); + cgs.media.pebbleMdl3 = trap_R_RegisterModel( "models/effects/pebble3.md3" ); + + if ( !cgs.media.pebbleMdl1 + || !cgs.media.pebbleMdl2 + || !cgs.media.pebbleMdl3 ) { + CG_Printf( "^3Unable to load pebble models, some of them are missing. Remember: must be in models/effects/pebble[1-3].md3 one per each, all 3 of them must be available" ); + } + // register the inline models cgs.numInlineModels = trap_CM_NumInlineModels(); for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { @@ -672,8 +857,25 @@ static void CG_RegisterGraphics( void ) { cgs.gameModels[i] = trap_R_RegisterModel( modelName ); } + // BFP - Monster gamemode, set model and skin for the player monster + // BFP - NOTE: Historically, on RC versions, the monster model pack went without player sounds + // and the game loaded the selected player model sounds that the user played + // on the other gamemodes + if ( cgs.gametype == GT_MONSTER && cgs.monster > 0 ) { + if ( CG_RegisterMonsterModel() ) { + CG_Printf( "loaded monster model\n" ); + } else { + CG_Printf( "unable to load monster model\n" ); + } + CG_RegisterMonsterSkin(); + CG_RegisterMonsterAnimations(); + CG_RegisterMonsterModelIcon(); + } + CG_ClearParticles (); -/* + +// BFP - Unused function for particles, looks like here is to determine in the areas +#if 0 for (i=1; i= sizeof(buf) ) { + trap_FS_FCloseFile( f ); + Com_Printf( "BFP config file too long\n" ); + return; + } + + // read the config file + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the config file line by line + ptr = buf; + + while ( *ptr ) { + // skip whitespace + while ( *ptr && ( *ptr == ' ' || *ptr == '\t' || *ptr == '\r' ) ) { + ptr++; + } + + if ( !*ptr ) { + break; + } + + // check command type + if ( *ptr == 'b' && *(ptr + 1) == ' ' ) { // "b " + char key[256], binding[512]; + int i; + + ptr += 2; // skip "b " + + // parse key + i = 0; + while ( *ptr && *ptr != ' ' && *ptr != '\n' ) { + key[i++] = *ptr++; + } + key[i] = 0; + + // skip space + while ( *ptr && *ptr == ' ' ) { + ptr++; + } + + // parse binding (may be quoted) + i = 0; + if ( *ptr == '"' ) { + ptr++; // skip opening quote + while ( *ptr && *ptr != '"' && *ptr != '\n' ) { + binding[i++] = *ptr++; + } + if ( *ptr == '"' ) { + ptr++; // skip closing quote + } + } else { + while ( *ptr && *ptr != '\n' ) { + binding[i++] = *ptr++; + } + } + binding[i] = 0; + + // execute! + Com_sprintf( cmd, sizeof( cmd ), "bind %s %s\n", key, binding ); + trap_SendConsoleCommand( cmd ); + } else if ( *ptr == 'c' && *(ptr + 1) == ' ' ) { // "c " + char cvar[256], value[512]; + int i; + + ptr += 2; // skip "c " + + // parse cvar name + i = 0; + while ( *ptr && *ptr != ' ' && *ptr != '\n' ) { + cvar[i++] = *ptr++; + } + cvar[i] = 0; + + // skip space + while ( *ptr && *ptr == ' ' ) { + ptr++; + } + + // parse value + i = 0; + while ( *ptr && *ptr != '\n' ) { + value[i++] = *ptr++; + } + value[i] = 0; + + // execute! + Com_sprintf( cmd, sizeof( cmd ), "seta %s %s\n", cvar, value ); + trap_SendConsoleCommand( cmd ); + } else if ( !Q_strncmp( ptr, "bfp", 3 ) ) { // "bfp" marks the end of file + break; + } + + // skip to next line + while ( *ptr && *ptr != '\n' ) { + ptr++; + } + if ( *ptr == '\n' ) { + ptr++; + } + } + + // Com_Printf( S_COLOR_GREEN "BFP config loaded successfully\n" ); +} + /* ================= CG_Init @@ -803,6 +1134,9 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); + // BFP - Initialize that shader for missile trails + cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); + CG_RegisterCvars(); CG_InitConsoleCommands(); @@ -818,6 +1152,10 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; +#ifdef USE_NEW_FONT_RENDERER + CG_LoadFonts(); +#endif + // get the gamestate from the client system trap_GetGameState( &cgs.gameState ); @@ -857,6 +1195,9 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { CG_InitMarkPolys(); + // BFP - Initialize trails + CG_InitTrails(); + // remove the last loading update cg.infoScreenText[0] = 0; @@ -867,6 +1208,9 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { CG_LoadingString( "" ); + // BFP - Load bfp.cfg + CG_LoadBFPConfig(); + CG_ShaderStateChanged(); trap_S_ClearLoopingSounds( qtrue ); diff --git a/source/cgame/cg_marks.c b/source/cgame/cg_marks.c index 7717308..1f8b8f5 100644 --- a/source/cgame/cg_marks.c +++ b/source/cgame/cg_marks.c @@ -65,7 +65,7 @@ CG_FreeMarkPoly ================== */ void CG_FreeMarkPoly( markPoly_t *le ) { - if ( !le->prevMark ) { + if ( !le->prevMark || !le->nextMark ) { CG_Error( "CG_FreeLocalEntity: not active" ); } @@ -291,1978 +291,3 @@ void CG_AddMarks( void ) { trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); } } - -// cg_particles.c - -#define BLOODRED 2 -#define EMISIVEFADE 3 -#define GREY75 4 - -typedef struct particle_s -{ - struct particle_s *next; - - float time; - float endtime; - - vec3_t org; - vec3_t vel; - vec3_t accel; - int color; - float colorvel; - float alpha; - float alphavel; - int type; - qhandle_t pshader; - - float height; - float width; - - float endheight; - float endwidth; - - float start; - float end; - - float startfade; - qboolean rotate; - int snum; - - qboolean link; - - // Ridah - int shaderAnim; - int roll; - - int accumroll; - -} cparticle_t; - -typedef enum -{ - P_NONE, - P_WEATHER, - P_FLAT, - P_SMOKE, - P_ROTATE, - P_WEATHER_TURBULENT, - P_ANIM, // Ridah - P_BAT, - P_BLEED, - P_FLAT_SCALEUP, - P_FLAT_SCALEUP_FADE, - P_WEATHER_FLURRY, - P_SMOKE_IMPACT, - P_BUBBLE, - P_BUBBLE_TURBULENT, - P_SPRITE -} particle_type_t; - -#define MAX_SHADER_ANIMS 32 -#define MAX_SHADER_ANIM_FRAMES 64 - -static char *shaderAnimNames[MAX_SHADER_ANIMS] = { - "explode1", - NULL -}; -static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; -static int shaderAnimCounts[MAX_SHADER_ANIMS] = { - 23 -}; -static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { - 1.0f -}; -static int numShaderAnims; -// done. - -#define PARTICLE_GRAVITY 40 -#define MAX_PARTICLES 1024 - -cparticle_t *active_particles, *free_particles; -cparticle_t particles[MAX_PARTICLES]; -int cl_numparticles = MAX_PARTICLES; - -qboolean initparticles = qfalse; -vec3_t pvforward, pvright, pvup; -vec3_t rforward, rright, rup; - -float oldtime; - -/* -=============== -CL_ClearParticles -=============== -*/ -void CG_ClearParticles (void) -{ - int i; - - memset( particles, 0, sizeof(particles) ); - - free_particles = &particles[0]; - active_particles = NULL; - - for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY - || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - {// create a front facing polygon - - if (p->type != P_WEATHER_FLURRY) - { - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - if (org[2] > p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - p->org[2] = ( p->start + crandom () * 4 ); - - - if (p->type == P_BUBBLE_TURBULENT) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - } - } - else - { - if (org[2] < p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - while (p->org[2] < p->end) - { - p->org[2] += (p->start - p->end); - } - - - if (p->type == P_WEATHER_TURBULENT) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - } - } - - - // Rafael snow pvs check - if (!p->link) - return; - - p->alpha = 1; - } - - // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp - if (Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - // done. - - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, -p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255 * p->alpha; - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy( point, TRIverts[0].xyz ); - TRIverts[0].st[0] = 1; - TRIverts[0].st[1] = 0; - TRIverts[0].modulate[0] = 255; - TRIverts[0].modulate[1] = 255; - TRIverts[0].modulate[2] = 255; - TRIverts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, TRIverts[1].xyz); - TRIverts[1].st[0] = 0; - TRIverts[1].st[1] = 0; - TRIverts[1].modulate[0] = 255; - TRIverts[1].modulate[1] = 255; - TRIverts[1].modulate[2] = 255; - TRIverts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, TRIverts[2].xyz); - TRIverts[2].st[0] = 0; - TRIverts[2].st[1] = 1; - TRIverts[2].modulate[0] = 255; - TRIverts[2].modulate[1] = 255; - TRIverts[2].modulate[2] = 255; - TRIverts[2].modulate[3] = 255 * p->alpha; - } - - } - else if (p->type == P_SPRITE) - { - vec3_t rr, ru; - vec3_t rotate_ang; - - VectorSet (color, 1.0, 1.0, 0.5); - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, pvup, point); - VectorMA (point, -width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, pvup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, pvup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) - {// create a front rotating facing polygon - - if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - - if (p->color == BLOODRED) - VectorSet (color, 0.22f, 0.0f, 0.0f); - else if (p->color == GREY75) - { - float len; - float greyit; - float val; - len = Distance (cg.snap->ps.origin, org); - if (!len) - len = 1; - - val = 4096/len; - greyit = 0.25 * val; - if (greyit > 0.5) - greyit = 0.5; - - VectorSet (color, greyit, greyit, greyit); - } - else - VectorSet (color, 1.0, 1.0, 1.0); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - if (cg.time > p->startfade) - { - invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); - - if (p->color == EMISIVEFADE) - { - float fval; - fval = (invratio * invratio); - if (fval < 0) - fval = 0; - VectorSet (color, fval , fval , fval ); - } - invratio *= p->alpha; - } - else - invratio = 1 * p->alpha; - - if (invratio > 1) - invratio = 1; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->type != P_SMOKE_IMPACT) - { - vec3_t temp; - - vectoangles (rforward, temp); - p->accumroll += p->roll; - temp[ROLL] += p->accumroll * 0.1; - AngleVectors ( temp, NULL, rright2, rup2); - } - else - { - VectorCopy (rright, rright2); - VectorCopy (rup, rup2); - } - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255 * invratio; - - } - else if (p->type == P_BLEED) - { - vec3_t rr, ru; - vec3_t rotate_ang; - float alpha; - - alpha = p->alpha; - - if (p->roll) - { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - else - { - VectorCopy (pvup, ru); - VectorCopy (pvright, rr); - } - - VectorMA (org, -p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 111; - verts[0].modulate[1] = 19; - verts[0].modulate[2] = 9; - verts[0].modulate[3] = 255 * alpha; - - VectorMA (org, -p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 111; - verts[1].modulate[1] = 19; - verts[1].modulate[2] = 9; - verts[1].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 111; - verts[2].modulate[1] = 19; - verts[2].modulate[2] = 9; - verts[2].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 111; - verts[3].modulate[1] = 19; - verts[3].modulate[2] = 9; - verts[3].modulate[3] = 255 * alpha; - - } - else if (p->type == P_FLAT_SCALEUP) - { - float width, height; - float sinR, cosR; - - if (p->color == BLOODRED) - VectorSet (color, 1, 1, 1); - else - VectorSet (color, 0.5, 0.5, 0.5); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (width > p->endwidth) - width = p->endwidth; - - if (height > p->endheight) - height = p->endheight; - - sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); - cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= sinR; - verts[0].xyz[1] -= cosR; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= cosR; - verts[1].xyz[1] += sinR; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += sinR; - verts[2].xyz[1] += cosR; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += cosR; - verts[3].xyz[1] -= sinR; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255; - } - else if (p->type == P_FLAT) - { - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= p->height; - verts[0].xyz[1] -= p->width; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= p->height; - verts[1].xyz[1] += p->width; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += p->height; - verts[2].xyz[1] += p->width; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += p->height; - verts[3].xyz[1] -= p->width; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - } - // Ridah - else if (p->type == P_ANIM) { - vec3_t rr, ru; - vec3_t rotate_ang; - int i, j; - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - if (ratio >= 1.0f) { - ratio = 0.9999f; - } - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - // if we are "inside" this sprite, don't draw - if (Distance( cg.snap->ps.origin, org ) < width/1.5) { - return; - } - - i = p->shaderAnim; - j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); - p->pshader = shaderAnims[i][j]; - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, pvup, point); - VectorMA (point, -width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, pvup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, pvup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - // done. - - if (!p->pshader) { -// (SA) temp commented out for DM -// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); - return; - } - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) - trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); - else - trap_R_AddPolyToScene( p->pshader, 4, verts ); - -} - -// Ridah, made this static so it doesn't interfere with other files -static float roll = 0.0; - -/* -=============== -CG_AddParticles -=============== -*/ -void CG_AddParticles (void) -{ - cparticle_t *p, *next; - float alpha; - float time, time2; - vec3_t org; - int color; - cparticle_t *active, *tail; - int type; - vec3_t rotate_ang; - - if (!initparticles) - CG_ClearParticles (); - - VectorCopy( cg.refdef.viewaxis[0], pvforward ); - VectorCopy( cg.refdef.viewaxis[1], pvright ); - VectorCopy( cg.refdef.viewaxis[2], pvup ); - - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - roll += ((cg.time - oldtime) * 0.1) ; - rotate_ang[ROLL] += (roll*0.9); - AngleVectors ( rotate_ang, rforward, rright, rup); - - oldtime = cg.time; - - active = NULL; - tail = NULL; - - for (p=active_particles ; p ; p=next) - { - - next = p->next; - - time = (cg.time - p->time)*0.001; - - alpha = p->alpha + time*p->alphavel; - if (alpha <= 0) - { // faded out - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - - } - - if (p->type == P_WEATHER_FLURRY) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - } - - - if (p->type == P_FLAT_SCALEUP_FADE) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - } - - if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { - // temporary sprite - CG_AddParticleToScene (p, p->org, alpha); - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - p->next = NULL; - if (!tail) - active = tail = p; - else - { - tail->next = p; - tail = p; - } - - if (alpha > 1.0) - alpha = 1; - - color = p->color; - - time2 = time*time; - - org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; - org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; - org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; - - type = p->type; - - CG_AddParticleToScene (p, org, alpha); - } - - active_particles = active; -} - -/* -====================== -CG_AddParticles -====================== -*/ -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - qboolean turb = qtrue; - - if (!pshader) - CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.90f; - p->alphavel = 0; - - p->start = cent->currentState.origin2[0]; - p->end = cent->currentState.origin2[1]; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->pshader = pshader; - - if (rand()%100 > 90) - { - p->height = 32; - p->width = 32; - p->alpha = 0.10f; - } - else - { - p->height = 1; - p->width = 1; - } - - p->vel[2] = -20; - - p->type = P_WEATHER_FLURRY; - - if (turb) - p->vel[2] = -10; - - VectorCopy(cent->currentState.origin, p->org); - - p->org[0] = p->org[0]; - p->org[1] = p->org[1]; - p->org[2] = p->org[2]; - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); - p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); - p->vel[2] += cent->currentState.angles[2]; - - if (turb) - { - p->accel[0] = crandom () * 16; - p->accel[1] = crandom () * 16; - } - -} - -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - p->height = 1; - p->width = 1; - - p->vel[2] = -50; - - if (turb) - { - p->type = P_WEATHER_TURBULENT; - p->vel[2] = -50 * 1.3; - } - else - { - p->type = P_WEATHER; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - float randsize; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - - randsize = 1 + (crandom() * 0.5); - - p->height = randsize; - p->width = randsize; - - p->vel[2] = 50 + ( crandom() * 10 ); - - if (turb) - { - p->type = P_BUBBLE_TURBULENT; - p->vel[2] = 50 * 1.3; - } - else - { - p->type = P_BUBBLE; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) -{ - - // using cent->density = enttime - // cent->frame = startfade - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSmoke == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->color = 0; - p->alpha = 1.0; - p->alphavel = 0; - p->start = cent->currentState.origin[2]; - p->end = cent->currentState.origin2[2]; - p->pshader = pshader; - p->rotate = qfalse; - p->height = 8; - p->width = 8; - p->endheight = 32; - p->endwidth = 32; - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org); - - p->vel[0] = p->vel[1] = 0; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[2] = 5; - - if (cent->currentState.frame == 1)// reverse gravity - p->vel[2] *= -1; - - p->roll = 8 + (crandom() * 4); -} - - -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) -{ - - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 1.0; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->accel[2] = -60; - p->vel[2] += -20; - -} - -/* -====================== -CG_ParticleExplosion -====================== -*/ - -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) -{ - cparticle_t *p; - int anim; - - if (animStr < (char *)10) - CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); - - // find the animation string - for (anim=0; shaderAnimNames[anim]; anim++) { - if (!Q_stricmp( animStr, shaderAnimNames[anim] )) - break; - } - if (!shaderAnimNames[anim]) { - CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); - return; - } - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.5; - p->alphavel = 0; - - if (duration < 0) { - duration *= -1; - p->roll = 0; - } else { - p->roll = crandom()*179; - } - - p->shaderAnim = anim; - - p->width = sizeStart; - p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction - - p->endheight = sizeEnd; - p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; - - p->endtime = cg.time + duration; - - p->type = P_ANIM; - - VectorCopy( origin, p->org ); - VectorCopy( vel, p->vel ); - VectorClear( p->accel ); - -} - -// Rafael Shrapnel -void CG_AddParticleShrapnel (localEntity_t *le) -{ - return; -} -// done. - -int CG_NewParticleArea (int num) -{ - // const char *str; - char *str; - char *token; - int type; - vec3_t origin, origin2; - int i; - float range = 0; - int turb; - int numparticles; - int snum; - - str = (char *) CG_ConfigString (num); - if (!str[0]) - return (0); - - // returns type 128 64 or 32 - token = COM_Parse (&str); - type = atoi (token); - - if (type == 1) - range = 128; - else if (type == 2) - range = 64; - else if (type == 3) - range = 32; - else if (type == 0) - range = 256; - else if (type == 4) - range = 8; - else if (type == 5) - range = 16; - else if (type == 6) - range = 32; - else if (type == 7) - range = 64; - - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin[i] = atof (token); - } - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin2[i] = atof (token); - } - - token = COM_Parse (&str); - numparticles = atoi (token); - - token = COM_Parse (&str); - turb = atoi (token); - - token = COM_Parse (&str); - snum = atoi (token); - - for (i=0; i= 4) - CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - else - CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - } - - return (1); -} - -void CG_SnowLink (centity_t *cent, qboolean particleOn) -{ - cparticle_t *p, *next; - int id; - - id = cent->currentState.frame; - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) - { - if (p->snum == id) - { - if (particleOn) - p->link = qtrue; - else - p->link = qfalse; - } - } - - } -} - -void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.25; - p->alphavel = 0; - p->roll = crandom()*179; - - p->pshader = pshader; - - p->endtime = cg.time + 1000; - p->startfade = cg.time + 100; - - p->width = rand()%4 + 8; - p->height = rand()%4 + 8; - - p->endheight = p->height *2; - p->endwidth = p->width * 2; - - p->endtime = cg.time + 500; - - p->type = P_SMOKE_IMPACT; - - VectorCopy( origin, p->org ); - VectorSet(p->vel, 0, 0, 20); - VectorSet(p->accel, 0, 0, 20); - - p->rotate = qtrue; -} - -void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - if (fleshEntityNum) - p->startfade = cg.time; - else - p->startfade = cg.time + 100; - - p->width = 4; - p->height = 4; - - p->endheight = 4+rand()%3; - p->endwidth = p->endheight; - - p->type = P_SMOKE; - - VectorCopy( start, p->org ); - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -20; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - p->alpha = 0.75; - -} - -void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - int time; - int time2; - float ratio; - - float duration = 1500; - - time = cg.time; - time2 = cg.time + cent->currentState.time; - - ratio =(float)1 - ((float)time / (float)time2); - - if (!pshader) - CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - p->startfade = p->endtime; - - p->width = 1; - p->height = 3; - - p->endheight = 3; - p->endwidth = 1; - - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org ); - - p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); - p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); - p->vel[2] = (cent->currentState.origin2[2]); - - p->snum = 1.0f; - - VectorClear( p->accel ); - - p->accel[2] = -20; - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - - -void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - if (cent->currentState.angles2[2]) - p->endtime = cg.time + cent->currentState.angles2[2]; - else - p->endtime = cg.time + 60000; - - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) - { - p->width = cent->currentState.angles2[0]; - p->height = cent->currentState.angles2[0]; - - p->endheight = cent->currentState.angles2[1]; - p->endwidth = cent->currentState.angles2[1]; - } - else - { - p->width = 8; - p->height = 8; - - p->endheight = 16; - p->endwidth = 16; - } - - p->type = P_FLAT_SCALEUP; - - p->snum = 1.0; - - VectorCopy(cent->currentState.origin, p->org ); - - p->org[2]+= 0.55 + (crandom() * 0.5); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - -void CG_OilSlickRemove (centity_t *cent) -{ - cparticle_t *p, *next; - int id; - - id = 1.0f; - - if (!id) - CG_Printf ("CG_OilSlickRevove NULL id\n"); - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_FLAT_SCALEUP) - { - if (p->snum == id) - { - p->endtime = cg.time + 100; - p->startfade = p->endtime; - p->type = P_FLAT_SCALEUP_FADE; - - } - } - - } -} - -qboolean ValidBloodPool (vec3_t start) -{ -#define EXTRUDE_DIST 0.5 - - vec3_t angles; - vec3_t right, up; - vec3_t this_pos, x_pos, center_pos, end_pos; - float x, y; - float fwidth, fheight; - trace_t trace; - vec3_t normal; - - fwidth = 16; - fheight = 16; - - VectorSet (normal, 0, 0, 1); - - vectoangles (normal, angles); - AngleVectors (angles, NULL, right, up); - - VectorMA (start, EXTRUDE_DIST, normal, center_pos); - - for (x= -fwidth/2; xendpos, start); - legit = ValidBloodPool (start); - - if (!legit) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + 3000; - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - rndSize = 0.4 + random()*0.6; - - p->width = 8*rndSize; - p->height = 8*rndSize; - - p->endheight = 16*rndSize; - p->endwidth = 16*rndSize; - - p->type = P_FLAT_SCALEUP; - - VectorCopy(start, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - p->color = BLOODRED; -} - -#define NORMALSIZE 16 -#define LARGESIZE 32 - -void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - p->endtime = cg.time + 350 + (crandom() * 100); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - p->endheight = LARGESIZE; - p->endwidth = LARGESIZE; - - p->type = P_SMOKE; - - VectorCopy( origin, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -1; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) -{ - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 0.4f; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->org[0] += (crandom() * x); - p->org[1] += (crandom() * y); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += (crandom() * 4); - p->vel[1] += (crandom() * 4); - p->vel[2] += (20 + (crandom() * 10)) * speed; - - p->accel[0] = crandom () * 4; - p->accel[1] = crandom () * 4; - -} - -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - VectorNegate (dir, dir); - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 5.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - // RF, stay around for long enough to expand and dissipate naturally - if (length) - p->endtime = cg.time + 4500 + (crandom() * 3500); - else - p->endtime = cg.time + 750 + (crandom() * 500); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - - // RF, expand while falling - p->endheight = LARGESIZE*3.0; - p->endwidth = LARGESIZE*3.0; - - if (!length) - { - p->width *= 0.2f; - p->height *= 0.2f; - - p->endheight = NORMALSIZE; - p->endwidth = NORMALSIZE; - } - - p->type = P_SMOKE; - - VectorCopy( point, p->org ); - - p->vel[0] = crandom()*6; - p->vel[1] = crandom()*6; - p->vel[2] = random()*20; - - // RF, add some gravity/randomness - p->accel[0] = crandom()*3; - p->accel[1] = crandom()*3; - p->accel[2] = -PARTICLE_GRAVITY*0.4; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = rand()%179; - - p->pshader = pshader; - - if (duration > 0) - p->endtime = cg.time + duration; - else - p->endtime = duration; - - p->startfade = cg.time; - - p->width = size; - p->height = size; - - p->endheight = size; - p->endwidth = size; - - p->type = P_SPRITE; - - VectorCopy( origin, p->org ); - - p->rotate = qfalse; -} - diff --git a/source/cgame/cg_particles.c b/source/cgame/cg_particles.c index d9c3e7e..76681a6 100644 --- a/source/cgame/cg_particles.c +++ b/source/cgame/cg_particles.c @@ -22,6 +22,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // Rafael particles // cg_particles.c +// BFP - HIGHLY MODIFIED + #include "cg_local.h" #define BLOODRED 2 @@ -39,15 +41,14 @@ typedef struct particle_s 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; @@ -56,82 +57,60 @@ typedef struct particle_s float startfade; qboolean rotate; - int snum; + int custom; - qboolean link; + qboolean stopped; // Ridah - int shaderAnim; - int roll; + int rollBounceCount; int accumroll; + // BFP - Entity num + int entityNum; + + // BFP - Model + qhandle_t pmodel; } cparticle_t; typedef enum { P_NONE, - P_WEATHER, - P_FLAT, + P_ANTIGRAV_ROCK, // BFP - Antigrav rock particles 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_AURA, // BFP - Particle aura + P_SPARK, // BFP - Beam struggle spark + P_ROCK_DEBRIS, // BFP - Bouncing rock fragment (explosion impact on ground) + P_WATER_SPLASH, // BFP - Water entry splash bubble (upward arc, stops at surface) P_SPRITE } particle_type_t; -#define MAX_SHADER_ANIMS 32 -#define MAX_SHADER_ANIM_FRAMES 64 - -static char *shaderAnimNames[MAX_SHADER_ANIMS] = { - "explode1", - "blacksmokeanim", - "twiltb2", - "expblue", - "blacksmokeanimb", // uses 'explode1' sequence - "blood", - NULL -}; -static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; -static int shaderAnimCounts[MAX_SHADER_ANIMS] = { - 23, - 25, - 45, - 25, - 23, - 5, -}; -static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { - 1.405f, - 1.0f, - 1.0f, - 1.0f, - 1.0f, - 1.0f, -}; -static int numShaderAnims; -// done. - #define PARTICLE_GRAVITY 40 -#define MAX_PARTICLES 1024 * 8 +#define MAX_PARTICLES (1024 * 6) +#define MAX_PARTICLES_3D (1024 * 3) + +static cparticle_t *active_particles, *free_particles; +static cparticle_t particles[MAX_PARTICLES]; +static int cl_numparticles = MAX_PARTICLES; + +static qboolean initparticles = qfalse; +static vec3_t vforward, vright, vup; +static vec3_t rforward, rright, rup; -cparticle_t *active_particles, *free_particles; -cparticle_t particles[MAX_PARTICLES]; -int cl_numparticles = MAX_PARTICLES; +static float oldtime; -qboolean initparticles = qfalse; -vec3_t vforward, vright, vup; -vec3_t rforward, rright, rup; +// BFP - NOTE: Particles use non-timescaled, before it was timescaled by game using cg.time +static int timenonscaled; -float oldtime; +#define NORMALSIZE 16 +#define LARGESIZE 32 + +// BFP - Function to handle bubbles +static void CG_BubblesWaterHandling( cparticle_t *p, vec3_t org ); +// BFP - Function to handle charge smoke particles +static void CG_ChargeSmokeHandling( cparticle_t *p, vec3_t org ); /* =============== @@ -142,6 +121,10 @@ void CG_ClearParticles (void) { int i; + // BFP - Limit the pool when 3D particles are active; the full array is + // always allocated but only the first cl_numparticles slots are chained + cl_numparticles = ( cg_3dparticles.integer > 0 ) ? MAX_PARTICLES_3D : MAX_PARTICLES; + memset( particles, 0, sizeof(particles) ); free_particles = &particles[0]; @@ -154,671 +137,527 @@ void CG_ClearParticles (void) } particles[cl_numparticles-1].next = NULL; - oldtime = cg.time; - - // Ridah, init the shaderAnims - for (i=0; shaderAnimNames[i]; i++) { - int j; - - for (j=0; jtype == 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; - } - - } - } - + if ( !p->pshader ) { + return; + } - // Rafael snow pvs check - if (!p->link) - return; + time = timenonscaled - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; - 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; - } - + width = p->width + ratio * ( p->endwidth - p->width ); + height = p->height + ratio * ( p->endheight - p->height ); + + if ( p->rollBounceCount ) + { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->rollBounceCount; + AngleVectors( rotate_ang, NULL, rr, ru ); + } + + if ( p->rollBounceCount ) + { + VectorMA( org, -height, ru, point ); + VectorMA( point, -width, rr, point ); } - else if (p->type == P_SPRITE) + else { - vec3_t rr, ru; - vec3_t rotate_ang; + VectorMA( org, -height, vup, point ); + VectorMA( point, -width, vright, point ); + } + VectorCopy( point, verts[0].xyz ); + Vector2Set( verts[0].st, 0, 0 ); + Byte4Set( verts[0].modulate, 255, 255, 255, 255 ); - VectorSet (color, 1.0, 1.0, 1.0); - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; + if ( p->rollBounceCount ) + { + VectorMA( point, 2*height, ru, point ); + } + else + { + VectorMA( point, 2*height, vup, point ); + } + VectorCopy( point, verts[1].xyz ); + Vector2Set( verts[1].st, 0, 1 ); + Byte4Set( verts[1].modulate, 255, 255, 255, 255 ); + + if ( p->rollBounceCount ) + { + VectorMA( point, 2*width, rr, point ); + } + else + { + VectorMA( point, 2*width, vright, point ); + } + VectorCopy( point, verts[2].xyz ); + Vector2Set( verts[2].st, 1, 1 ); + Byte4Set( verts[2].modulate, 255, 255, 255, 255 ); - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); + if ( p->rollBounceCount ) + { + VectorMA( point, -2*height, ru, point ); + } + else + { + VectorMA( point, -2*height, vup, point ); + } + VectorCopy( point, verts[3].xyz ); + Vector2Set( verts[3].st, 1, 0 ); + Byte4Set( verts[3].modulate, 255, 255, 255, 255 ); - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } + trap_R_AddPolyToScene( p->pshader, 4, verts ); +} - 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; +/* +=================== +AddSparkParticle +=================== +*/ +static void AddSparkParticle( cparticle_t *p, vec3_t org, float alpha ) +{ + vec3_t forward, right, endPoint; + polyVert_t verts[4]; + float lifeFraction = (p->endtime - timenonscaled) / (p->endtime - p->time); + const float SPARK_MAX_LENGTH = 50.0f; + const float SPARK_MIN_LENGTH = 0.5f; + const float SPARK_MIN_WIDTH = 1.0f; + float length = SPARK_MIN_LENGTH + (SPARK_MAX_LENGTH - SPARK_MIN_LENGTH) * lifeFraction; + float currentWidth = SPARK_MIN_WIDTH + (p->width - SPARK_MIN_WIDTH) * lifeFraction; + + if ( !p->pshader ) { + return; } - 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; - } + p->width = currentWidth; - if (p->color == BLOODRED) - VectorSet (color, 0.22f, 0.0f, 0.0f); - else if (p->color == GREY75) - { - float len; - float greyit; - float val; - len = Distance (cg.snap->ps.origin, org); - if (!len) - len = 1; - - val = 4096/len; - greyit = 0.25 * val; - if (greyit > 0.5) - greyit = 0.5; - - VectorSet (color, greyit, greyit, greyit); - } - else - VectorSet (color, 1.0, 1.0, 1.0); + // direction of the particle + VectorNormalize2( p->vel, forward ); - 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) ); + VectorMA( org, length, forward, endPoint ); + PerpendicularVector( right, forward ); - 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; + // bottom-left + VectorMA( org, -p->width, right, verts[0].xyz ); + Vector2Set( verts[0].st, 0, 0 ); + Byte4Set( verts[0].modulate, 255, 255, 255, 255 * alpha ); - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - invratio = 1; + // top-left + VectorMA( org, p->width, right, verts[1].xyz ); + Vector2Set( verts[1].st, 0, 1 ); + Byte4Set( verts[1].modulate, 255, 255, 255, 255 * alpha ); - if (invratio > 1) - invratio = 1; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); + // top-right (tip narrower) + VectorMA( endPoint, p->width * 0.2f, right, verts[2].xyz ); + Vector2Set( verts[2].st, 1, 1 ); + Byte4Set( verts[2].modulate, 255, 255, 255, 255 * alpha ); - if (p->type != P_SMOKE_IMPACT) - { - vec3_t temp; + // bottom-right = top-right (triangle) + VectorCopy( verts[2].xyz, verts[3].xyz ); + Vector2Set( verts[3].st, 1, 0 ); + Byte4Set( verts[3].modulate, 255, 255, 255, 255 * alpha ); - vectoangles (rforward, temp); - p->accumroll += p->roll; - temp[ROLL] += p->accumroll * 0.1; - AngleVectors ( temp, NULL, rright2, rup2); - } - else - { - VectorCopy (rright, rright2); - VectorCopy (rup, rup2); - } - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, -p->width, vright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, p->width, vright, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, p->height, vup, point); - VectorMA (point, p->width, vright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else + trap_R_AddPolyToScene( p->pshader, 4, verts ); +} + +/* +=================== +AddGenericParticle +=================== +*/ +static void AddGenericParticle( cparticle_t *p, vec3_t org, float alpha ) +{ + vec3_t point; + polyVert_t verts[4]; + float width, height; + float time, time2, ratio, invratio; + vec3_t color = {1.0f, 1.0f, 1.0f}; + vec3_t rright2, rup2; + + time = timenonscaled - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg.snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + if ( timenonscaled > p->startfade ) + { + invratio = 1.0f - ( (float)(timenonscaled - p->startfade) / (float)(p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) { - VectorMA (org, p->height, vup, point); - VectorMA (point, -p->width, vright, point); + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); } - 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; - + invratio *= p->alpha; } - else if (p->type == P_BLEED) + else { - vec3_t rr, ru; - vec3_t rotate_ang; - float alpha; + invratio = p->alpha; + } - alpha = p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - alpha = 1; + // BFP - Don't disappear opaquely the bubbles even the debris + if ( invratio > 1.0f + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT + || p->type == P_ROCK_DEBRIS || p->type == P_WATER_SPLASH ) + { + invratio = 1.0f; + } - if (p->roll) - { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); + width = p->width + ratio * ( p->endwidth - p->width ); + height = p->height + ratio * ( p->endheight - p->height ); + + // compute rotated axes for non-smoke and non-aura types + if ( p->type != P_SMOKE && p->type != P_AURA + && p->type != P_BUBBLE && p->type != P_BUBBLE_TURBULENT + && p->type != P_WATER_SPLASH ) + { + vec3_t temp; + vectoangles( rforward, temp ); + if ( p->stopped ) { + p->rollBounceCount = 0; } - else - { - VectorCopy (vup, ru); - VectorCopy (vright, rr); + if ( p->rollBounceCount > 0 ) { + p->accumroll += p->rollBounceCount; + temp[ROLL] += p->accumroll * 0.1f; } - - 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; - + AngleVectors( temp, NULL, rright2, rup2 ); } - else if (p->type == P_FLAT_SCALEUP) + else { - 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; + VectorCopy( rright, rright2 ); + VectorCopy( rup, rup2 ); } - else if (p->type == P_FLAT) + + // BFP - Bubble types here + if ( p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) { + CG_BubblesWaterHandling( p, org ); - 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; + // BFP - Apply more end time to remove particles if the player stops charging + if ( p->type == P_BUBBLE ) { + if ( p->entityNum == cg.snap->ps.clientNum + && ( cg.snap->ps.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + && !p->stopped ) + { + p->endtime = timenonscaled + 2500 + (crandom() * 150); + p->stopped = qtrue; + } + } + } + // BFP - Charge smoke particle handling here + if ( p->type == P_SMOKE && p->custom == 1 ) + { + CG_ChargeSmokeHandling( p, org ); } - // Ridah - else if (p->type == P_ANIM) { - vec3_t rr, ru; - vec3_t rotate_ang; - int i, j; - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - if (ratio >= 1.0f) { - ratio = 0.9999f; - } - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); + // BFP - Antigrav rock type + if ( p->type == P_ANTIGRAV_ROCK ) + { + // BFP - When the particle, checked to be fallen, won't be reactivated when entering ki charging status again + if ( p->stopped ) + { + // BFP - To detect if there is something solid + trace_t trace; + vec3_t rockMins = {0, 0, -2}; // place a bit above + // contents should be CONTENTS_SOLID, so the particles don't touch any entity like the player + CG_Trace( &trace, p->org, rockMins, rockMins, org, -1, CONTENTS_SOLID ); + + p->time = timenonscaled; + p->custom = 1; // handle the p->custom when already entered in this phase for correction of client side visuals + // not hit anything or not a collider + if ( trace.fraction == 1.0f ) + { + VectorCopy( org, p->org ); + p->vel[2] -= 50; + p->accel[2] -= 200; + } + else // bouncing + { + if ( trace.plane.normal[2] >= 0.7f && Q_fabs( p->vel[2] ) < 1.0f ) { + // stop bouncing + VectorClear( p->vel ); + VectorClear( p->accel ); + VectorCopy( org, p->org ); + } + else + { + // similar to CG_ReflectVelocity + const float BOUNCEFACTOR = 0.5f; + float dot = DotProduct( p->vel, trace.plane.normal ); + if ( trace.plane.normal[2] < 0.91f && VectorLength( p->vel ) < 110.0f ) { + VectorMA( p->vel, 3.0f * dot, trace.plane.normal, p->vel ); + VectorScale( p->vel, 3.0f, p->vel ); + } else { + VectorMA( p->vel, -2.0f * dot, trace.plane.normal, p->vel ); + VectorScale( p->vel, BOUNCEFACTOR, p->vel ); + } + VectorCopy( trace.endpos, p->org ); + } + } + } - // if we are "inside" this sprite, don't draw - if (Distance( cg.snap->ps.origin, org ) < width/1.5) { + // BFP - When reaching into this top, remove the particle! + if ( org[2] > p->end ) + { + p->next = NULL; + p->color = p->alpha = 0; return; } + } + else if ( p->type == P_ROCK_DEBRIS || p->type == P_WATER_SPLASH ) + { + // BFP - To detect if there is something solid + trace_t trace; + vec3_t debrisMins = {0, 0, -2}; // place a bit above + int contents; + // contents should be CONTENTS_SOLID, so the particles don't touch any entity like the player + CG_Trace( &trace, p->org, debrisMins, debrisMins, org, -1, CONTENTS_SOLID ); + + // keep detecting the position, also helps to pass through map bounds + VectorCopy( org, p->org ); + + p->time = timenonscaled; + // not hit anything or not a collider + contents = trap_CM_PointContents( trace.endpos, 0 ); + + // bouncing rock + if ( p->rollBounceCount <= 0 && !p->stopped ) { // stop + VectorClear( p->accel ); + VectorClear( p->vel ); + } - i = p->shaderAnim; - j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); - p->pshader = shaderAnims[i][j]; + if ( trace.fraction == 1.0f ) { + p->vel[2] -= (p->stopped) ? 100 : 80; + p->accel[2] -= (p->stopped) ? 10 : 100; + } + else // bouncing + { + if ( p->stopped ) { // water touching something solid + VectorClear( p->vel ); + VectorClear( p->accel ); + VectorCopy( trace.endpos, p->org ); + p->height = p->width *= 0.9f; // make it tinier when that happens + } - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); + if ( trace.plane.normal[2] >= 0.7f && Q_fabs( p->vel[2] ) < 1.0f ) { + // stop bouncing + VectorClear( p->vel ); + VectorClear( p->accel ); + p->height = p->width *= 0.9f; + } + else + { + // similar to CG_ReflectVelocity + const float BOUNCEFACTOR = 0.55f; + float dot = DotProduct( p->vel, trace.plane.normal ); + if ( trace.plane.normal[2] < 0.91f && VectorLength( p->vel ) < 110.0f ) { + VectorMA( p->vel, 3.0f * dot, trace.plane.normal, p->vel ); + VectorScale( p->vel, 3.0f, p->vel ); + } else { + VectorMA( p->vel, -2.5f * dot, trace.plane.normal, p->vel ); + VectorScale( p->vel, BOUNCEFACTOR, p->vel ); + } + } } - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, vup, point); - VectorMA (point, -width, vright, point); + // if it's assigned to water, then detect when going underwater and changing to P_BUBBLE type + if ( p->stopped && ( contents & CONTENTS_WATER ) ) { + p->type = P_BUBBLE_TURBULENT; + p->endtime = timenonscaled + 600; + VectorCopy( trace.endpos, p->org ); + p->vel[2] = p->accel[2] = 0; } - 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); + + // for a short time, the debris begin to get tinier + if ( p->time > p->endtime - 500 ) { + p->height = p->width *= 0.9f; } - 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); + } + + // BFP - Render as 3D model + if ( cg_3dparticles.integer > 0 && p->pmodel + && ( p->type == P_ANTIGRAV_ROCK || p->type == P_AURA || p->type == P_ROCK_DEBRIS + || p->type == P_WATER_SPLASH || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) ) + { + refEntity_t modelRef; + vec3_t angles; + + memset( &modelRef, 0, sizeof(modelRef) ); + modelRef.reType = RT_MODEL; + modelRef.hModel = p->pmodel; + if ( p->type == P_AURA || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT || p->type == P_WATER_SPLASH ) { + modelRef.customShader = p->pshader; } - 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); + + // BFP - Advance yaw and pitch only while the rock is floating (ki active) + // endwidth/endheight hold per-particle spin speeds set at spawn + // startfade is reused as the pitch accumulator + if ( p->type == P_ANTIGRAV_ROCK && !p->stopped ) { + p->accumroll += p->endwidth; + if ( p->accumroll >= 360.0f ) { + p->accumroll -= 360.0f; + } + p->startfade += p->endheight; + if ( p->startfade >= 360.0f ) { + p->startfade -= 360.0f; + } } - 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; + + AxisClear( modelRef.axis ); + VectorSet( angles, + ( p->type == P_ANTIGRAV_ROCK ) ? p->startfade : 0, + (float)p->accumroll, + 0 ); + AnglesToAxis( angles, modelRef.axis ); + + VectorCopy( org, modelRef.origin ); + VectorCopy( org, modelRef.oldorigin ); + + VectorScale( modelRef.axis[0], p->width, modelRef.axis[0] ); + VectorScale( modelRef.axis[1], p->width, modelRef.axis[1] ); + VectorScale( modelRef.axis[2], p->width, modelRef.axis[2] ); + modelRef.nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( &modelRef ); + return; } - // done. - - if (!p->pshader) { + + // build the quad (non-3D case) + if ( !p->pshader ) { // (SA) temp commented out for DM // CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); return; } - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) - trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + 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 ); + Vector2Set( verts[0].st, 0, 0 ); + Byte4Set( verts[0].modulate, 255 * color[0], 255 * color[1], 255 * color[2], 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 ); + Vector2Set( verts[1].st, 0, 1 ); + Byte4Set( verts[1].modulate, 255 * color[0], 255 * color[1], 255 * color[2], 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 ); + Vector2Set( verts[2].st, 1, 1 ); + Byte4Set( verts[2].modulate, 255 * color[0], 255 * color[1], 255 * color[2], 255 * invratio ); + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } else - trap_R_AddPolyToScene( p->pshader, 4, verts ); + { + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + } + VectorCopy( point, verts[3].xyz ); + Vector2Set( verts[3].st, 1, 0 ); + Byte4Set( verts[3].modulate, 255 * color[0], 255 * color[1], 255 * color[2], 255 * invratio ); + + trap_R_AddPolyToScene( p->pshader, 4, verts ); +} +/* +===================== +CG_AddParticleToScene +===================== +*/ +void CG_AddParticleToScene (cparticle_t *p, vec3_t org, float alpha) +{ + // BFP - Don't draw if the particles are very far + if ( Distance( cg.snap->ps.origin, org ) > 20000 ) { + return; + } + + switch (p->type) + { + case P_SPRITE: + AddSpriteParticle(p, org, alpha); + break; + case P_SPARK: + AddSparkParticle(p, org, alpha); + break; + default: + AddGenericParticle(p, org, alpha); + break; + } } + // Ridah, made this static so it doesn't interfere with other files static float roll = 0.0; @@ -833,11 +672,11 @@ void CG_AddParticles (void) float alpha; float time, time2; vec3_t org; - int color; cparticle_t *active, *tail; - int type; vec3_t rotate_ang; + timenonscaled = trap_Milliseconds(); // BFP - That's what the variable makes non-timescaled + if (!initparticles) CG_ClearParticles (); @@ -846,128 +685,88 @@ void CG_AddParticles (void) VectorCopy( cg.refdef.viewaxis[2], vup ); vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - roll += ((cg.time - oldtime) * 0.1) ; + roll += ((timenonscaled - oldtime) * 0.1) ; rotate_ang[ROLL] += (roll*0.9); AngleVectors ( rotate_ang, rforward, rright, rup); - oldtime = cg.time; + oldtime = timenonscaled; active = NULL; tail = NULL; for (p=active_particles ; p ; p=next) { - next = p->next; - time = (cg.time - p->time)*0.001; + time = (timenonscaled - p->time)*0.001; - alpha = p->alpha + time*p->alphavel; - if (alpha <= 0) + // BFP - Make alpha timescaled for smoke-like particles + alpha = p->alpha + time*p->alphavel*(cg_timescale.value <= 0.1 ? 0.1 : cg_timescale.value); + if (p->alphavel < 0.0f) p->alpha = alpha; // BFP - Alpha fading out + if (p->alpha <= 0) { // faded out p->next = free_particles; free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; + p->type = p->color = p->alpha = 0; continue; } - if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + if (p->type == P_SMOKE + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT // BFP - Add P_BUBBLE types to remove particles + || p->type == P_ANTIGRAV_ROCK // BFP - Add P_ANTIGRAV_ROCK to remove particles + || p->type == P_AURA // BFP - Add P_AURA to remove particles + || p->type == P_ROCK_DEBRIS // BFP - Add P_ROCK_DEBRIS to remove particles + || p->type == P_WATER_SPLASH // BFP - Add P_WATER_SPLASH to remove particles + || p->type == P_SPARK) // BFP - Add P_SPARK to remove particles { - if (cg.time > p->endtime) + if (timenonscaled > p->endtime) { p->next = free_particles; free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - + p->color = p->alpha = 0; + p->height = p->width = p->endheight = p->endwidth = 0; continue; } + } + if (p->type == P_SPRITE && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->color = p->alpha = 0; + continue; } - if (p->type == P_WEATHER_FLURRY) + p->next = NULL; + if (!tail) + active = tail = p; + else { - 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; + 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) +void CG_ParticleBubble (centity_t *cent, qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t origin2, int turbtime, float range, float size) { cparticle_t *p; - qboolean turb = qtrue; - if (!pshader) - CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + // if (!pshader) CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); if (!free_particles) return; @@ -975,536 +774,351 @@ void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) free_particles = p->next; p->next = active_particles; active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.90f; - p->alphavel = 0; + p->time = timenonscaled; - p->start = cent->currentState.origin2[0]; - p->end = cent->currentState.origin2[1]; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->pshader = pshader; - - if (rand()%100 > 90) - { - p->height = 32; - p->width = 32; - p->alpha = 0.10f; - } - else - { - p->height = 1; - p->width = 1; - } + // BFP - Keep entity number to identify who is using + p->entityNum = cent->currentState.number; - p->vel[2] = -20; + // BFP - Add end time to remove particles, if there's no end time the particles will remain there + p->endtime = timenonscaled + 600; + p->startfade = timenonscaled + 200; - 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; + // BFP - Monster gamemode, player monster bubble particles last a bit more + if ( cgs.gametype == GT_MONSTER + && ( cent->currentState.eFlags & EF_MONSTER ) ) { + p->endtime += 200; + p->startfade += 200; } -} - -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; p->color = 0; - p->alpha = 0.40f; + p->alpha = 1; p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; + // BFP - Apply to entity's origin + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.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; + p->pmodel = pmodel; + p->height = p->width = (rand() % (int)size) + 1; + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->width = p->width * 0.075; + p->height = p->width; } - 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; + VectorCopy(origin, p->org); - if (turb) + if (turbtime) { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} + p->type = P_BUBBLE_TURBULENT; + // BFP - Apply end time to remove particles in that case, if there's no end time the particles will remain there + p->endtime = timenonscaled + turbtime; + p->height = p->width = (rand() % 2) + size; + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->width = p->width * 0.075; + p->height = p->width; + } -void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - float randsize; + // BFP - Monster gamemode, player monster bubble particles has different spawning origin + if ( cgs.gametype == GT_MONSTER + && ( cent->currentState.eFlags & EF_MONSTER ) ) { + p->org[0] += (crandom() * range); + p->org[1] += (crandom() * range); + } - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + VectorSet( p->vel, + crandom() * 300, + crandom() * 300, + 30 * (rand() % (int)range) ); - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - - randsize = 1 + (crandom() * 0.5); - - p->height = randsize; - p->width = randsize; - - p->vel[2] = 50 + ( crandom() * 10 ); + // dispersion + VectorSet( p->accel, + crandom() * 10, + crandom() * 10, + 20 * (rand() % (int)range) ); - if (turb) - { - p->type = P_BUBBLE_TURBULENT; - p->vel[2] = 50 * 1.3; + // avoid if both upwards are zero or less + if ( p->vel[2] <= 0 ) p->vel[2] = 10 + (rand() % (int)range); + if ( p->accel[2] <= 0 ) p->accel[2] = 10 + (rand() % (int)range); } else { + // spawn in one point + float angle = crandom() * M_PI; + float radius = crandom() * range; + 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->org[0] += cos( angle ) * radius; + p->org[1] += sin( angle ) * radius; + p->org[2] += (crandom() * 5); - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; + VectorSet( p->vel, + crandom() * 360, + crandom() * 360, + 20 ); - if (turb) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; + // dispersion + VectorSet( p->accel, + crandom() * 10, + crandom() * 10, + 1200 ); } - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - + p->accumroll = 0; + p->custom = 3 - (crandom() * 6); // used to randomize where the bubbles stop when these touches the surface + p->stopped = qfalse; // used to handle the bubbles when touching the surface } -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) -{ - - // using cent->density = enttime - // cent->frame = startfade - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSmoke == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->color = 0; - p->alpha = 1.0; - p->alphavel = 0; - p->start = cent->currentState.origin[2]; - p->end = cent->currentState.origin2[2]; - p->pshader = pshader; - p->rotate = qfalse; - p->height = 8; - p->width = 8; - p->endheight = 32; - p->endwidth = 32; - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org); - - p->vel[0] = p->vel[1] = 0; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[2] = 5; +// BFP - Handle bubble particles when reaching to the top +static void CG_BubblesWaterHandling( cparticle_t *p, vec3_t org ) { + trace_t trace; + vec3_t start, end; + int contents; - if (cent->currentState.frame == 1)// reverse gravity - p->vel[2] *= -1; - - p->roll = 8 + (crandom() * 4); -} + VectorCopy( org, end ); + end[2] -= 1; + VectorCopy( org, start ); + start[2] += 10; -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) -{ + // decelerate + if ( Q_fabs(p->vel[0]) > 0 ) { + p->vel[0] *= 0.995; + } + if ( Q_fabs(p->vel[1]) > 0 ) { + p->vel[1] *= 0.995; + } - cparticle_t *p; + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, vec3_origin, vec3_origin, 0, CONTENTS_WATER ); - if (!free_particles) + // if the particle is touching something solid, it will skip instead stopping + contents = trap_CM_PointContents( trace.endpos, 0 ); + if ( contents & CONTENTS_SOLID ) { // remove when grazing something solid + p->next = NULL; + p->color = p->alpha = 0; 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); + } + if ( !( contents & CONTENTS_WATER ) ) { + p->time = timenonscaled; + VectorCopy (trace.endpos, p->org); + p->org[2] = trace.endpos[2] - p->custom; - 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; + // stop going up and decrease dispersion speed + p->vel[2] = 0; + VectorClear( p->accel ); - p->accel[2] = -60; - p->vel[2] += -20; - + // trace again if the bubble went outside, then set it near to the surface + contents = trap_CM_PointContents( p->org, 0 ); + if ( !( contents & CONTENTS_WATER ) ) { + VectorCopy (trace.endpos, p->org); + } + if ( p->type == P_BUBBLE ) { + if ( p->vel[0] != 0 ) p->vel[0] *= 0.9; + if ( p->vel[1] != 0 ) p->vel[1] *= 0.9; + // stop after few milliseconds + if ( p->stopped && p->time > p->endtime - 2250 ) { + p->vel[0] = 0; + p->vel[1] = 0; + } + } else { + if ( p->vel[0] != 0 ) p->vel[0] *= 0.97; + if ( p->vel[1] != 0 ) p->vel[1] *= 0.97; + } + } } -/* -====================== -CG_ParticleExplosion -====================== -*/ - -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +// BFP - Particle for dash smoke when using ki boost and moving on the ground +void CG_ParticleDashSmoke (centity_t *cent, qhandle_t pshader, vec3_t origin, float size, float velocityDisp, float upVelocity, float accel) { cparticle_t *p; - int anim; - if (animStr < (char *)10) - CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + // if (!pshader) CG_Printf ("CG_ParticleDashSmoke pshader == ZERO!\n"); - // find the animation string - for (anim=0; shaderAnimNames[anim]; anim++) { - if (!stricmp( animStr, shaderAnimNames[anim] )) - break; + // BFP - Don't spawn on pause + if ( cg.frametime <= 0.0f ) { + return; } - if (!shaderAnimNames[anim]) { - CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + + // Too much smoke... + // That cent->trailTime can be handled to avoid spawning too much and only spawn when the game isn't paused, hehehe :P + if ( cent->trailTime > cg.time ) { return; } + cent->trailTime += 35; + if ( cent->trailTime < cg.time ) { + cent->trailTime = cg.time; + } 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->time = timenonscaled; - p->shaderAnim = anim; + // BFP - Keep entity number to identify who is using + p->entityNum = cent->currentState.clientNum; - p->width = sizeStart; - p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + p->alpha = 0.45; + p->alphavel = -0.1; - p->endheight = sizeEnd; - p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + p->pshader = pshader; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; - p->endtime = cg.time + duration; + p->endtime = timenonscaled + 2000; + p->startfade = timenonscaled + 100; - p->type = P_ANIM; + p->height = p->width = size; - VectorCopy( origin, p->org ); - VectorCopy( vel, p->vel ); - VectorClear( p->accel ); + p->endheight = p->height * 2; + p->endwidth = p->width * 2; -} + p->type = P_SMOKE; -// Rafael Shrapnel -void CG_AddParticleShrapnel (localEntity_t *le) -{ - return; + VectorCopy( origin, p->org ); + VectorSet( p->vel, + (rand() % (int)velocityDisp) - velocityDisp * 0.5, + (rand() % (int)velocityDisp) - velocityDisp * 0.5, + upVelocity ); + + // dispersion + VectorSet( p->accel, + crandom() * accel, + crandom() * accel, + 1800 ); + + p->stopped = qfalse; // to distinguish the type of smoke } -// done. -int CG_NewParticleArea (int num) +// BFP - Particle for charge smoke when using ki charge near the ground +void CG_ParticleChargeSmoke (centity_t *cent, qhandle_t pshader, vec3_t origin, float size, float radialVel, float baseRadius) { - // 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); - } + cparticle_t *p; + float angle, radius; + vec3_t dir, radial, angular; - 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); + // if (!pshader) CG_Printf ("CG_ParticleChargeSmoke pshader == ZERO!\n"); - token = COM_Parse (&str); - snum = atoi (token); - - for (i=0; i= 4) - CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - else - CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + // Too much smoke... + // That cent->pe.chargeSmokeTime can be handled to avoid spawning too much and only spawn when the game isn't paused, hehehe :P + // It isn't possible reusing cent->trailTime, it would have client visual issues +#if 0 + if ( cent->pe.chargeSmokeTime > timenonscaled ) { + return; } - - 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; - } - } - + cent->pe.chargeSmokeTime += 10; + if ( cent->pe.chargeSmokeTime < timenonscaled ) { + cent->pe.chargeSmokeTime = timenonscaled; } -} - -void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); +#endif if (!free_particles) return; + p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; - p->time = cg.time; - p->alpha = 0.25; - p->alphavel = 0; - p->roll = crandom()*179; + + p->time = timenonscaled; + + // BFP - Keep entity number to identify who is using + p->entityNum = cent->currentState.clientNum; + + p->alpha = 0.31; + p->alphavel = -0.09; p->pshader = pshader; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; - p->endtime = cg.time + 1000; - p->startfade = cg.time + 100; + p->endtime = timenonscaled + 2000; + p->startfade = timenonscaled + 100; - p->width = rand()%4 + 8; - p->height = rand()%4 + 8; + p->height = p->width = size; - p->endheight = p->height *2; + p->endheight = p->height * 2; p->endwidth = p->width * 2; - p->endtime = cg.time + 500; - - p->type = P_SMOKE_IMPACT; + p->type = P_SMOKE; VectorCopy( origin, p->org ); - VectorSet(p->vel, 0, 0, 20); - VectorSet(p->accel, 0, 0, 20); - p->rotate = qtrue; -} - -void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) -{ - cparticle_t *p; + // randomize angle and radius for circular motion + angle = crandom() * 360.0f; + radius = baseRadius + crandom() * 10; // radius around the origin - if (!pshader) - CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + // compute radial direction + dir[0] = cos( DEG2RAD( angle ) ); + dir[1] = sin( DEG2RAD( angle ) ); + dir[2] = 0; - 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; + // initial position offset for circular spread + VectorMA( origin, radius, dir, p->org ); - p->pshader = pshader; + // velocity for circular motion + VectorScale( dir, radialVel, radial ); // radial component of velocity + VectorSet( angular, -dir[1], dir[0], 0 ); // perpendicular for tangential motion + VectorScale( angular, 100, angular ); // tangential speed - p->endtime = cg.time + duration; - - if (fleshEntityNum) - p->startfade = cg.time; - else - p->startfade = cg.time + 100; + // combine radial and angular velocity + p->vel[0] = radial[0] + angular[0]; + p->vel[1] = radial[1] + angular[1]; + p->vel[2] = radial[2] + angular[2]; - p->width = 4; - p->height = 4; + // vertical lift + p->vel[2] = 50 + crandom() * 10; - p->endheight = 4+rand()%3; - p->endwidth = p->endheight; + // dispersion + VectorSet( p->accel, + crandom() * 20, + crandom() * 20, + 5); - p->type = P_SMOKE; + p->stopped = qfalse; + p->custom = 1; +} - VectorCopy( start, p->org ); - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -20; - VectorClear( p->accel ); +// BFP - Handle charge smoke particles when touching something solid +static void CG_ChargeSmokeHandling( cparticle_t *p, vec3_t org ) { + trace_t trace; + vec3_t start, end; + int contents; - p->rotate = qfalse; + VectorCopy( org, start ); + VectorCopy( org, end ); - p->roll = rand()%179; - - p->color = BLOODRED; - p->alpha = 0.75; + // trace to check the collision + trap_CM_BoxTrace( &trace, start, end, vec3_origin, vec3_origin, 0, CONTENTS_SOLID ); + contents = trap_CM_PointContents( trace.endpos, 0 ); + if ( contents & CONTENTS_SOLID ) { // remove when grazing something solid + p->next = NULL; + p->color = p->alpha = 0; + } } -void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +// BFP - Antigrav rock particles for ki charging status +void CG_ParticleAntigravRock (qhandle_t pshader, qhandle_t pmodel, centity_t *cent, int entityNum, vec3_t origin, float size, float spawnRange, float endTime) { cparticle_t *p; - int time; - int time2; - float ratio; - - float duration = 1500; - - time = cg.time; - time2 = cg.time + cent->currentState.time; + // if (!pshader) CG_Printf ("CG_ParticleAntigravRock == ZERO!\n"); - ratio =(float)1 - ((float)time / (float)time2); - - if (!pshader) - CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + // Too many rocks... That cent->dustTrailTime can be handled to avoid spawning too many, hehehe :P + // cent->dustTrailTime was unused on Q3 before, so now it's being used for this kind of particles + // reusing cent->trailTime would make the time more delayed to spawn the particles, so not visually good + if ( cent->dustTrailTime > timenonscaled ) { + return; + } + cent->dustTrailTime += 20; + if ( cent->dustTrailTime < timenonscaled ) { + cent->dustTrailTime = timenonscaled; + } if (!free_particles) return; @@ -1512,52 +1126,83 @@ void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) 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->time = timenonscaled; - p->endtime = cg.time + duration; - - p->startfade = p->endtime; + // BFP - Keep entity number to identify who is using + p->entityNum = entityNum; - p->width = 1; - p->height = 3; + p->endtime = timenonscaled + endTime + (crandom() * 20); + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->endtime += 350; + } - p->endheight = 3; - p->endwidth = 1; + p->color = 0; + p->alpha = 1; + p->alphavel = 0; + p->pshader = pshader; + p->pmodel = pmodel; + p->height = p->width = (rand() % (int)size) + size; + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->width *= 12; + p->height = p->width; + } + p->type = P_ANTIGRAV_ROCK; - p->type = P_SMOKE; + VectorCopy( origin, p->org ); - VectorCopy(cent->currentState.origin, p->org ); - - p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); - p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); - p->vel[2] = (cent->currentState.origin2[2]); + p->org[0] += (crandom() * spawnRange); + p->org[1] += (crandom() * spawnRange); - p->snum = 1.0f; + p->start = cent->currentState.origin[2]; + p->end = p->org[2] + 800 + (crandom() * 10); - VectorClear( p->accel ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 450; + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->vel[2] = 300; + } - p->accel[2] = -20; + p->accel[0] = 0; + p->accel[1] = 0; + p->accel[2] = 20; - p->rotate = qfalse; + p->accumroll = rand() % 360; + p->startfade = crandom() * 360; // initial pitch (degrees) - reused field + p->endwidth = 1.5f + (crandom() * 2.5f); // yaw spin speed (deg/frame) - reused field + p->endheight = 0.8f + (crandom() * 1.5f); // pitch spin speed (deg/frame) - reused field + p->stopped = qfalse; // to handle the ki charging status + p->custom = 0; // to handle the client side visuals +} - p->roll = rand()%179; - - p->alpha = 0.75; +// BFP - To handle the client side visuals of antigrav rock particles +void CG_AntigravRockHandling (centity_t *cent) +{ + cparticle_t *p, *next; + for (p=active_particles ; p ; p=next) + { + next = p->next; + if ( p->type != P_ANTIGRAV_ROCK ) continue; + + if ( p->entityNum == cent->currentState.clientNum + && !( cent->currentState.eFlags & EF_DEAD ) + && ( ( !( cent->currentState.eFlags & EF_AURA ) && !( cent->currentState.eFlags & EF_AURA_TIER_UP ) ) + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + || ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_CHARGE && !( cent->currentState.eFlags & EF_AURA ) && !( cent->currentState.eFlags & EF_AURA_TIER_UP ) ) ) + && !p->stopped ) { // BFP - Make each particle fall when they aren't on ki charging status + p->endtime = timenonscaled + 1650; + p->stopped = qtrue; + } + } } - -void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +// BFP - Particle aura +void CG_ParticleAura (centity_t *cent, int entityNum, qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t origin2, float range) { cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + // if (!pshader) CG_Printf ("CG_ParticleAura pshader == ZERO!\n"); if (!free_particles) return; @@ -1565,274 +1210,132 @@ void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) 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->time = timenonscaled; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; + // BFP - Keep entity number to identify who is using + p->entityNum = entityNum; - p->pshader = pshader; - - if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) - { - p->width = cent->currentState.angles2[0]; - p->height = cent->currentState.angles2[0]; + // BFP - Add end time to remove particles, if there's no end time the particles will remain there + p->endtime = timenonscaled + 400; + p->startfade = timenonscaled + 200; - p->endheight = cent->currentState.angles2[1]; - p->endwidth = cent->currentState.angles2[1]; - } - else - { - p->width = 8; - p->height = 8; + p->color = 0; + p->alpha = 0.5; + p->alphavel = -0.075; - p->endheight = 16; - p->endwidth = 16; + // BFP - Apply to player's origin + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->pmodel = pmodel; + p->height = p->width = 40; + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->width *= 0.015f; + p->height = p->width; } - p->type = P_FLAT_SCALEUP; - - p->snum = 1.0; - - VectorCopy(cent->currentState.origin, p->org ); - - p->org[2]+= 0.55 + (crandom() * 0.5); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; + VectorCopy(origin, p->org); - p->roll = rand()%179; - - p->alpha = 0.75; + p->type = P_AURA; + + p->org[0] += (crandom() * range); + p->org[1] += (crandom() * range); + p->org[2] += (crandom() * 5); + + VectorSet( p->vel, + crandom() * 360, + crandom() * 360, + 100 ); + + // dispersion + VectorSet( p->accel, + crandom() * 20, + crandom() * 20, + 1200 ); + + // BFP - Monster gamemode, player monster aura particles are bigger + if ( cent->currentState.eFlags & EF_MONSTER ) { + p->org[2] -= 50; + p->height *= 3.75; + p->width *= 3.75; + VectorScale( p->vel, 3.75, p->vel ); + VectorScale( p->accel, 3.75, p->accel ); + } + p->stopped = qfalse; } -void CG_OilSlickRemove (centity_t *cent) +// BFP - To handle the client side visuals of aura particle +void CG_ParticleAuraHandling (centity_t *cent) { cparticle_t *p, *next; - int id; - - id = 1.0f; - - if (!id) - CG_Printf ("CG_OilSlickRevove NULL id\n"); for (p=active_particles ; p ; p=next) { next = p->next; - - if (p->type == P_FLAT_SCALEUP) - { - if (p->snum == id) - { - p->endtime = cg.time + 100; - p->startfade = p->endtime; - p->type = P_FLAT_SCALEUP_FADE; - - } + if ( p->type != P_AURA ) continue; + + if ( p->entityNum == cent->currentState.clientNum + && !( cent->currentState.eFlags & EF_DEAD ) + && !( cent->currentState.eFlags & EF_AURA ) + && !p->stopped ) { // BFP - Make each particle fall when there's no aura at this moment + p->alphavel = -0.03; + p->accel[0] = 0; + p->accel[1] = 0; + p->endtime = timenonscaled + 600; + p->stopped = qtrue; } - } } -qboolean ValidBloodPool (vec3_t start) +// BFP - Spawn a bouncing rock fragment (explosion debris on ground) +void CG_ParticleRockDebris (qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t vel, float size, float velocity, float accel) { -#define EXTRUDE_DIST 0.5 - - vec3_t angles; - vec3_t right, up; - vec3_t this_pos, x_pos, center_pos, end_pos; - float x, y; - float fwidth, fheight; - trace_t trace; - vec3_t normal; - - fwidth = 16; - fheight = 16; - - VectorSet (normal, 0, 0, 1); - - vectoangles (normal, angles); - AngleVectors (angles, NULL, right, up); - - VectorMA (start, EXTRUDE_DIST, normal, center_pos); - - for (x= -fwidth/2; xendpos, start); - legit = ValidBloodPool (start); - - if (!legit) - return; - p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + 3000; - p->startfade = p->endtime; + p->time = timenonscaled; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; + p->startfade = timenonscaled + 200; + p->endtime = timenonscaled + 1200; + p->color = 0; + p->alpha = 1; + p->alphavel = 0; p->pshader = pshader; + p->pmodel = pmodel; + p->height = p->width = (rand() % 6) + 3; + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->width *= 12; + p->height = p->width; + } - rndSize = 0.4 + random()*0.6; - - p->width = 8*rndSize; - p->height = 8*rndSize; - - p->endheight = 16*rndSize; - p->endwidth = 16*rndSize; - - p->type = P_FLAT_SCALEUP; - - VectorCopy(start, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - p->color = BLOODRED; -} - -#define NORMALSIZE 16 -#define LARGESIZE 32 - -void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); + p->type = P_ROCK_DEBRIS; - for (i=0; iorg ); + p->start = origin[2]; - 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; - - 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 ); + VectorCopy( vel, p->vel ); - p->rotate = qfalse; + p->accel[0] = (crandom() * accel); + p->accel[1] = (crandom() * accel); + p->accel[2] = accel + (crandom() * 50); - p->roll = rand()%179; - - p->color = BLOODRED; - - p->alpha = 0.75; - - } + p->rollBounceCount = 3; // bounce counter + p->stopped = qfalse; + p->custom = 0; - + // BFP - Random fixed yaw per rock + p->accumroll = rand() % 360; } -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +// BFP - Spawn a water entry splash bubble (arcs upward, stops at water surface) +void CG_ParticleWaterSplash (qhandle_t pshader, qhandle_t pmodel, vec3_t origin, vec3_t vel, float size, float velocity, float accel) { cparticle_t *p; @@ -1842,177 +1345,102 @@ void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, free_particles = p->next; p->next = active_particles; active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 0.4f; - p->alphavel = 0; + p->time = timenonscaled; - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; + p->startfade = timenonscaled + 200; + p->endtime = timenonscaled + 2450 + (crandom() * 20); - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); + p->color = 0; + p->alpha = 1; + p->alphavel = 0; + p->pshader = pshader; + p->pmodel = pmodel; + p->height = p->width = (rand() % (int)size) + size * 0.335f; + if ( cg_3dparticles.integer > 0 && pmodel ) { + p->width *= 0.075f; + p->height = p->width; + } - p->org[0] += (crandom() * x); - p->org[1] += (crandom() * y); + p->type = P_WATER_SPLASH; - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; + VectorCopy( origin, p->org ); + p->org[0] += (crandom() * 15); + p->org[1] += (crandom() * 15); + p->start = origin[2]; - p->accel[0] = p->accel[1] = p->accel[2] = 0; + p->vel[0] = (crandom() * velocity); + p->vel[1] = (crandom() * velocity); + p->vel[2] = velocity * 7; - p->vel[0] += (crandom() * 4); - p->vel[1] += (crandom() * 4); - p->vel[2] += (20 + (crandom() * 10)) * speed; + p->accel[0] = (crandom() * accel); + p->accel[1] = (crandom() * accel); + p->accel[2] = accel + (crandom() * 50); - p->accel[0] = crandom () * 4; - p->accel[1] = crandom () * 4; - + p->rollBounceCount = 0; + p->accumroll = 0; + p->stopped = qtrue; // stops when it reaches the water surface + p->custom = 0; } -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +void CG_ParticleSparks (qhandle_t pshader, vec3_t origin, vec3_t vel) { - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; cparticle_t *p; - int i; - - dist = 0; - - VectorNegate (dir, dir); - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 5.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - // RF, stay around for long enough to expand and dissipate naturally - if (length) - p->endtime = cg.time + 4500 + (crandom() * 3500); - else - p->endtime = cg.time + 750 + (crandom() * 500); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - - // RF, expand while falling - p->endheight = LARGESIZE*3.0; - p->endwidth = LARGESIZE*3.0; - - if (!length) - { - p->width *= 0.2f; - p->height *= 0.2f; - - p->endheight = NORMALSIZE; - p->endwidth = NORMALSIZE; - } - - p->type = P_SMOKE; - - VectorCopy( point, p->org ); - - p->vel[0] = crandom()*6; - p->vel[1] = crandom()*6; - p->vel[2] = random()*20; + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = timenonscaled; + p->endtime = timenonscaled + 250; - // RF, add some gravity/randomness - p->accel[0] = crandom()*3; - p->accel[1] = crandom()*3; - p->accel[2] = -PARTICLE_GRAVITY*0.4; + p->color = 0; + p->alpha = 1; + p->alphavel = 0; + p->pshader = pshader; + p->height = p->width = (rand() % 6) + 5; - VectorClear( p->accel ); + p->type = P_SPARK; - p->rotate = qfalse; + VectorCopy( origin, p->org ); + p->start = origin[2]; + VectorCopy( vel, p->vel ); - p->roll = rand()%179; - - p->alpha = 0.75; - - } + p->accel[0] = (crandom() * 600); + p->accel[1] = (crandom() * 600); + p->accel[2] = 50 + (crandom() * 25); - + p->rollBounceCount = 0; + p->stopped = qfalse; + p->custom = 1; // enable gravity } -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +void CG_ParticleBeamStruggleSpark (qhandle_t pshader, vec3_t origin, vec3_t vel) { 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; + p->time = timenonscaled; + p->endtime = timenonscaled + 250; VectorCopy( origin, p->org ); + p->start = origin[2]; + VectorCopy( vel, p->vel ); + + p->accel[0] = (crandom() * 300); + p->accel[1] = (crandom() * 300); + p->accel[2] = -10 - (crandom() * 20); - p->rotate = qfalse; + p->type = P_SPARK; + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->pshader = pshader; + p->height = p->width = (rand() % 4) + 2; } diff --git a/source/cgame/cg_players.c b/source/cgame/cg_players.c index 1fc004c..91ff4b7 100644 --- a/source/cgame/cg_players.c +++ b/source/cgame/cg_players.c @@ -34,7 +34,7 @@ char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { "*pain100_1.wav", "*falling1.wav", "*gasp.wav", - "*drown.wav", + // "*drown.wav", // BFP - No drowning "*fall1.wav", "*taunt.wav" }; @@ -87,7 +87,7 @@ Read a configuration file containing animation coutns and rates models/players/visor/animation.cfg, etc ====================== */ -static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { +qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { char *text_p, *prev; int len; int i; @@ -102,7 +102,11 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( f == FS_INVALID_HANDLE ) { + return qfalse; + } if ( len <= 0 ) { + trap_FS_FCloseFile( f ); return qfalse; } if ( len >= sizeof( text ) - 1 ) { @@ -110,7 +114,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) return qfalse; } trap_FS_Read( text, len, f ); - text[len] = 0; + text[len] = '\0'; trap_FS_FCloseFile( f ); // parse the text @@ -127,12 +131,12 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { @@ -152,7 +156,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } ci->headOffset[i] = atof( token ); @@ -160,7 +164,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) continue; } else if ( !Q_stricmp( token, "sex" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( token[0] == 'f' || token[0] == 'F' ) { @@ -184,14 +188,14 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) text_p = prev; // unget the token break; } - Com_Printf( "unknown token '%s' is %s\n", token, filename ); + Com_Printf( "unknown token '%s' in %s\n", token, filename ); } // read information for each frame for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; @@ -214,7 +218,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) } token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { break; } animations[i].numFrames = atoi( token ); @@ -228,13 +232,13 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) } token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { break; } animations[i].loopFrames = atoi( token ); token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { break; } fps = atof( token ); @@ -246,16 +250,17 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) } if ( i != MAX_ANIMATIONS ) { - CG_Printf( "Error parsing animation file: %s", filename ); + CG_Printf( "Error parsing animation file: %s\n", filename ); return qfalse; } + // BFP - memcpy is not necessary, that can be removed // crouch backward animation - memcpy(&animations[LEGS_WALKCR], &animations[LEGS_WALKCR], sizeof(animation_t)); // BFP - Crouch backwards animation tweak - animations[LEGS_WALKCR].reversed = qtrue; + // memcpy(&animations[LEGS_WALKCR], &animations[LEGS_WALKCR], sizeof(animation_t)); // BFP - Crouch backwards animation tweak + animations[LEGS_WALKCR].reversed = qfalse; // BFP - Make the duck walking forward only // walk backward animation - memcpy(&animations[LEGS_WALK], &animations[LEGS_WALK], sizeof(animation_t)); // BFP - Walk backwards animation tweak - animations[LEGS_WALK].reversed = qtrue; + // memcpy(&animations[LEGS_WALK], &animations[LEGS_WALK], sizeof(animation_t)); // BFP - Walk backwards animation tweak + animations[LEGS_WALK].reversed = qfalse; // BFP - Make the walk moving forward only // flag moving fast animations[FLAG_RUN].firstFrame = 0; animations[FLAG_RUN].numFrames = 16; @@ -294,23 +299,32 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) CG_FileExists ========================== */ -static qboolean CG_FileExists(const char *filename) { +static qboolean CG_FileExists( const char *filename ) { int len; + fileHandle_t f; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + + if ( f != FS_INVALID_HANDLE ) { + trap_FS_FCloseFile( f ); + } - len = trap_FS_FOpenFile( filename, 0, FS_READ ); - if (len>0) { + if ( len > 0 ) { return qtrue; } + return qfalse; } +// BFP - No CG_FindClientModelFile, remove in the future? +#if 0 /* ========================== CG_FindClientModelFile ========================== */ static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { - char *team, *charactersFolder; + char *team; int i; if ( cgs.gametype >= GT_TEAM ) { @@ -328,16 +342,15 @@ static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t else { team = "default"; } - charactersFolder = ""; while(1) { for ( i = 0; i < 2; i++ ) { if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_lily_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); + // "models/players/james/stroggs/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s/%s%s_%s_%s.%s", modelName, teamName, base, skinName, team, ext ); } else { - // "models/players/characters/james/lower_lily_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); + // "models/players/james/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s/%s_%s_%s.%s", modelName, base, skinName, team, ext ); } if ( CG_FileExists( filename ) ) { return qtrue; @@ -347,27 +360,27 @@ static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t #if 0 if ( cgs.gametype >= GT_TEAM ) { if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); + // "models/players/james/stroggs/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s/%s%s_%s.%s", modelName, teamName, base, team, ext ); } else { - // "models/players/characters/james/lower_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); + // "models/players/james/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s/%s_%s.%s", modelName, base, team, ext ); } } else { if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_lily.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); + // "models/players/james/stroggs/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s/%s%s_%s.%s", modelName, teamName, base, skinName, ext ); } else { - // "models/players/characters/james/lower_lily.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); + // "models/players/james/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s/%s_%s.%s", modelName, base, skinName, ext ); } } #endif - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); + Com_sprintf( filename, length, "models/players/%s/%s_%s.%s", modelName, base, skinName, ext ); if ( CG_FileExists( filename ) ) { return qtrue; } @@ -375,22 +388,18 @@ static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t break; } } - // if tried the heads folder first - if ( charactersFolder[0] ) { - break; - } - charactersFolder = "characters/"; } return qfalse; } +#endif /* ========================== CG_FindClientHeadFile ========================== */ -static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { +qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { char *team, *headsFolder; int i; @@ -475,28 +484,53 @@ CG_RegisterClientSkin static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { char filename[MAX_QPATH]; - /* - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); + // BFP - That was a way to load the skins, so CG_FindClientModelFile function does the job, + // but it tends to have more memory load. Also the characters folder check is disabled, BFP doesn't have any check of this + + // BFP - Legs skin + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); ci->legsSkin = trap_R_RegisterSkin( filename ); if (!ci->legsSkin) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); - ci->legsSkin = trap_R_RegisterSkin( filename ); - if (!ci->legsSkin) { - Com_Printf( "Leg skin load failure: %s\n", filename ); - } + Com_Printf( "Leg skin load failure: %s\n", filename ); } + // BFP - Ultimate tier legs skin + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjlower.skin", modelName, skinName ); + ci->ultTierLegsSkin = trap_R_RegisterSkin( filename ); + // if (!ci->ultTierLegsSkin) { + // Com_Printf( "Ultimate tier leg skin load failure: %s\n", filename ); + // } - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); + // BFP - Torso skin + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); ci->torsoSkin = trap_R_RegisterSkin( filename ); if (!ci->torsoSkin) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); - ci->torsoSkin = trap_R_RegisterSkin( filename ); - if (!ci->torsoSkin) { - Com_Printf( "Torso skin load failure: %s\n", filename ); - } + Com_Printf( "Torso skin load failure: %s\n", filename ); } - */ + + // BFP - Ultimate tier torso skin + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjtorso.skin", modelName, skinName ); + ci->ultTierTorsoSkin = trap_R_RegisterSkin( filename ); + // if (!ci->ultTierTorsoSkin) { + // Com_Printf( "Ultimate tier torso skin load failure: %s\n", filename ); + // } + + // BFP - Head skin + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", modelName, skinName ); + ci->headSkin = trap_R_RegisterSkin( filename ); + if (!ci->headSkin) { + Com_Printf( "Head skin load failure: %s\n", filename ); + } + + // BFP - Ultimate tier head skin + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjhead.skin", modelName, skinName ); + ci->ultTierHeadSkin = trap_R_RegisterSkin( filename ); + // if (!ci->ultTierHeadSkin) { + // Com_Printf( "Ultimate tier head skin load failure: %s\n", filename ); + // } + + // BFP - Uses more memory load, not recommended, remove in the future? +#if 0 if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { ci->legsSkin = trap_R_RegisterSkin( filename ); } @@ -517,6 +551,7 @@ static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, c if (!ci->headSkin) { Com_Printf( "Head skin load failure: %s\n", filename ); } +#endif // if any skins failed to load if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { @@ -535,6 +570,9 @@ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelN const char *headName; char newTeamName[MAX_QPATH*2]; + // BFP - Most stuff is modified here to reduce memory load, + // anyone can modify and insert their own customized player folder/load error messages + if ( headModelName[0] == '\0' ) { headName = modelName; } @@ -544,42 +582,60 @@ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelN Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); ci->legsModel = trap_R_RegisterModel( filename ); if ( !ci->legsModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); - ci->legsModel = trap_R_RegisterModel( filename ); - if ( !ci->legsModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; } + // BFP - Ultimate tier legs model + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjlegs.md3", modelName ); + ci->ultTierLegsModel = trap_R_RegisterModel( filename ); + /*if ( !ci->ultTierLegsModel ) { + Com_Printf( "Failed to load ultimate tier legs model file %s\n", filename ); + return qfalse; + }*/ + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); ci->torsoModel = trap_R_RegisterModel( filename ); if ( !ci->torsoModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); - ci->torsoModel = trap_R_RegisterModel( filename ); - if ( !ci->torsoModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; } - if( headName[0] == '*' ) { - Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); - } - else { + // BFP - Ultimate tier torso model + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjtorso.md3", modelName ); + ci->ultTierTorsoModel = trap_R_RegisterModel( filename ); + /*if ( !ci->ultTierTorsoModel ) { + Com_Printf( "Failed to load ultimate tier torso model file %s\n", filename ); + return qfalse; + }*/ + + // if( headName[0] == '*' ) { + // Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); + // } + // else { Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headName ); - } + // } ci->headModel = trap_R_RegisterModel( filename ); + /* // if the head model could not be found and we didn't load from the heads folder try to load from there if ( !ci->headModel && headName[0] != '*' ) { Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); ci->headModel = trap_R_RegisterModel( filename ); } + */ if ( !ci->headModel ) { Com_Printf( "Failed to load model file %s\n", filename ); return qfalse; } + // BFP - Ultimate tier head model + Com_sprintf( filename, sizeof( filename ), "models/players/%s/ssjhead.md3", modelName ); + ci->ultTierHeadModel = trap_R_RegisterModel( filename ); + /*if ( !ci->ultTierHeadModel ) { + Com_Printf( "Failed to load ultimate tier head model file %s\n", filename ); + return qfalse; + }*/ + // if any skins failed to load, return failure if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { if ( teamName && *teamName) { @@ -603,11 +659,8 @@ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelN // load the animations Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); if ( !CG_ParseAnimationFile( filename, ci ) ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); - if ( !CG_ParseAnimationFile( filename, ci ) ) { - Com_Printf( "Failed to load animation file %s\n", filename ); - return qfalse; - } + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; } if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { @@ -624,6 +677,8 @@ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelN return qtrue; } + // BFP - No color1 +#if 0 /* ==================== CG_ColorFromString @@ -651,6 +706,7 @@ static void CG_ColorFromString( const char *v, vec3_t color ) { color[0] = 1.0f; } } +#endif /* =================== @@ -671,7 +727,9 @@ static void CG_LoadClientInfo( clientInfo_t *ci ) { modelloaded = qtrue; if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { if ( cg_buildScript.integer ) { - CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); + // BFP - Use a different error message like original BFP + // CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); + CG_Error( "Model \"%s\" not found.\n", ci->modelName ); modelloaded = qfalse; // BFP - if the model didn't load } @@ -694,6 +752,9 @@ static void CG_LoadClientInfo( clientInfo_t *ci ) { } } #endif + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } } ci->newAnims = qfalse; @@ -737,6 +798,8 @@ static void CG_LoadClientInfo( clientInfo_t *ci ) { } } +// BFP - Unused static functions, remove? I think so +#if 0 /* ====================== CG_CopyClientInfoModel @@ -870,6 +933,7 @@ static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { CG_LoadClientInfo( ci ); } +#endif /* @@ -900,20 +964,23 @@ void CG_NewClientInfo( int clientNum ) { v = Info_ValueForKey(configstring, "n"); Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + // BFP - No color1 +#if 0 // colors v = Info_ValueForKey( configstring, "c1" ); CG_ColorFromString( v, newInfo.color1 ); - - v = Info_ValueForKey( configstring, "c2" ); - CG_ColorFromString( v, newInfo.color2 ); +#endif // bot skill v = Info_ValueForKey( configstring, "skill" ); newInfo.botSkill = atoi( v ); + // BFP - No handicap +#if 0 // handicap v = Info_ValueForKey( configstring, "hc" ); newInfo.handicap = atoi( v ); +#endif // wins v = Info_ValueForKey( configstring, "w" ); @@ -927,22 +994,10 @@ void CG_NewClientInfo( int clientNum ) { v = Info_ValueForKey( configstring, "t" ); newInfo.team = atoi( v ); - // team task - v = Info_ValueForKey( configstring, "tt" ); - newInfo.teamTask = atoi(v); - - // team leader - v = Info_ValueForKey( configstring, "tl" ); - newInfo.teamLeader = atoi(v); - - v = Info_ValueForKey( configstring, "g_redteam" ); - Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); - - v = Info_ValueForKey( configstring, "g_blueteam" ); - Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); - // model v = Info_ValueForKey( configstring, "model" ); + // BFP - No force model (In the future, remove cg_forceModel, which wasn't removed originally?) +#if 0 if ( cg_forceModel.integer ) { // forcemodel makes everyone use a single model // to prevent load hitches @@ -971,22 +1026,27 @@ void CG_NewClientInfo( int clientNum ) { Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); } } + } else +#endif + Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + + slash = strchr( newInfo.modelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); } else { - Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *slash = 0; + } - slash = strchr( newInfo.modelName, '/' ); - if ( !slash ) { - // modelName didn not include a skin name - Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); - } else { - Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); - // truncate modelName - *slash = 0; - } + // BFP - cg_forceSkin, forces the default skin for each model + if ( cg_forceSkin.integer ) { + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); } - // head model - v = Info_ValueForKey( configstring, "hmodel" ); + // BFP - No force model (In the future, remove cg_forceModel, which wasn't removed originally?) +#if 0 if ( cg_forceModel.integer ) { // forcemodel makes everyone use a single model // to prevent load hitches @@ -1015,23 +1075,33 @@ void CG_NewClientInfo( int clientNum ) { Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); } } + } else +#endif + + // BFP - Set the head model name as the same as model name + Q_strncpyz( newInfo.headModelName, newInfo.modelName, sizeof( newInfo.headModelName ) ); + slash = strchr( newInfo.headModelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); } else { - Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); + Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); + // truncate modelName + *slash = 0; + } - slash = strchr( newInfo.headModelName, '/' ); - if ( !slash ) { - // modelName didn not include a skin name - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } else { - Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); - // truncate modelName - *slash = 0; - } + // BFP - cg_forceSkin, forces the default skin for each model + if ( cg_forceSkin.integer ) { + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); } + // BFP - Change the model without impeding with defer + // BFP - TODO: Remove cg_deferPlayers cvar and its unnecessary code in the future +#if 0 // scan for an existing clientinfo that matches this modelname // so we can avoid loading checks if possible - if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { + // if ( !CG_ScanForExistingClientInfo( &newInfo ) ) + // { qboolean forceDefer; forceDefer = trap_MemoryRemaining() < 4000000; @@ -1046,9 +1116,10 @@ void CG_NewClientInfo( int clientNum ) { newInfo.deferred = qfalse; } } else { +#endif CG_LoadClientInfo( &newInfo ); - } - } + // } + // } // replace whatever was there with the new one newInfo.infoValid = qtrue; @@ -1244,12 +1315,9 @@ static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float // if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { // BFP - When using ki boost use the following speed as haste powerup - if ( cent->currentState.number == cg.snap->ps.clientNum - && ( cent->currentState.eFlags & EF_AURA ) - && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE - && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE - && ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND - && ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_CHARGE ) { + if ( ( cent->currentState.eFlags & EF_AURA ) + && ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_RUN + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_BACK ) ) { speedScale = 1.5; // when using ki boost } else { speedScale = 1; @@ -1398,9 +1466,18 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v // --------- yaw ------------- + // BFP - Allow yaw while flying too // allow yaw to drift a bit - if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE - || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { + if ( ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) + && ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_FLYIDLE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) + && ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLECR + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STUN ) + && ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STUN ) + && ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_CHARGE ) ) { // if not standing still, always point all in the same direction cent->pe.torso.yawing = qtrue; // always center cent->pe.torso.pitching = qtrue; // always center @@ -1420,9 +1497,10 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; + // BFP - Swing the angles to make the movements look smooth // torso - CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); - CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + CG_SwingAngles( torsoAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); // BFP - Before: 25, 90 + CG_SwingAngles( legsAngles[YAW], 90, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); // BFP - Before: 40, 90 torsoAngles[YAW] = cent->pe.torso.yawAngle; legsAngles[YAW] = cent->pe.legs.yawAngle; @@ -1436,7 +1514,14 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v } else { dest = headAngles[PITCH] * 0.75f; } - CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); + // BFP - When flying, set the legs in the first case + if ( cent->currentState.eFlags & EF_FLIGHT ) { + CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.legs.pitchAngle, ¢->pe.legs.pitching ); + legsAngles[PITCH] = cent->pe.legs.pitchAngle; + } + + // BFP - When flying, set the torso correctly into these angles + CG_SwingAngles( dest, 30, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); torsoAngles[PITCH] = cent->pe.torso.pitchAngle; // @@ -1450,6 +1535,8 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v // --------- roll ------------- + // BFP - Prevent the player's head from tilting + headAngles[ROLL] = 0; // lean towards the direction of travel VectorCopy( cent->currentState.pos.trDelta, velocity ); @@ -1458,7 +1545,11 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v vec3_t axis[3]; float side; - speed *= 0.01f; // BFP - adjust legs speed, before 0.05f + // BFP - Speed handling when moving too much the angles + if ( speed < -480.0f ) speed = -480.0f; + if ( speed > 480.0f ) speed = 480.0f; + + speed *= 0.03f; // BFP - Adjust speed when rotate the angles (not a starting velocity), before: 0.05f AnglesToAxis( legsAngles, axis ); side = speed * DotProduct( velocity, axis[1] ); @@ -1466,8 +1557,17 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v side = speed * DotProduct( velocity, axis[0] ); legsAngles[PITCH] += side; + + // BFP - Make the torso move the pitch angle a bit in the flight + if ( cent->currentState.eFlags & EF_FLIGHT ) { + AnglesToAxis( torsoAngles, axis ); + side = speed * DotProduct( velocity, axis[0] ); + torsoAngles[PITCH] += side; + } } + // BFP - Don't make every player forced to see this way with their legs to the others +#if 0 // clientNum = cent->currentState.clientNum; if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { @@ -1478,13 +1578,7 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v legsAngles[ROLL] = 0.0f; } } - - // BFP - when flying, set correctly into these angles - if ( cg.predictedPlayerState.pm_flags & PMF_FLYING ) { - VectorCopy( cent->lerpAngles, headAngles ); - VectorCopy( cent->lerpAngles, torsoAngles ); - VectorCopy( cent->lerpAngles, legsAngles ); - } +#endif // pain twitch CG_AddPainTwitch( cent, torsoAngles ); @@ -1500,6 +1594,8 @@ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], v //========================================================================== +// BFP - No smoke puff effect +#if 0 /* =============== CG_HasteTrail @@ -1513,6 +1609,7 @@ static void CG_HasteTrail( centity_t *cent ) { if ( cent->trailTime > cg.time ) { return; } + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; if ( anim != LEGS_RUN && anim != LEGS_BACK ) { return; @@ -1527,17 +1624,18 @@ static void CG_HasteTrail( centity_t *cent ) { origin[2] -= 16; smoke = CG_SmokePuff( origin, vec3_origin, - 8, - 1, 1, 1, 1, - 500, - cg.time, - 0, - 0, - cgs.media.hastePuffShader ); + 8, + 1, 1, 1, 1, + 500, + cg.time, + 0, + 0, + cgs.media.hastePuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } +#endif /* =============== @@ -1549,6 +1647,11 @@ static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { vec3_t angles; vec3_t axis[3]; + // BFP - Don't show the model to the player itself + if ( cent->currentState.clientNum == cg.snap->ps.clientNum ) { + return; + } + VectorCopy( cent->lerpAngles, angles ); angles[PITCH] = 0; angles[ROLL] = 0; @@ -1701,12 +1804,13 @@ static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); } + // BFP - No flight powerup +#if 0 // flight plays a looped sound - /* if ( powerups & ( 1 << PW_FLIGHT ) ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); } - */ +#endif ci = &cgs.clientinfo[ cent->currentState.clientNum ]; // redflag @@ -1731,21 +1835,13 @@ static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); } - // neutralflag - if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if (ci->newAnims) { - CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); - } - else { - CG_TrailItem( cent, cgs.media.neutralFlagModel ); - } - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); - } - + // BFP - No haste powerup handling +#if 0 // haste leaves smoke trails if ( powerups & ( 1 << PW_HASTE ) ) { CG_HasteTrail( cent ); } +#endif } @@ -1760,7 +1856,8 @@ static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { int rf; refEntity_t ent; - if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + // BFP - Don't show the float sprite to the player itself, only in mirrors + if ( cent->currentState.number == cg.snap->ps.clientNum ) { // && !cg.renderingThirdPerson ) { rf = RF_THIRD_PERSON; // only show in mirrors } else { rf = 0; @@ -1768,7 +1865,7 @@ static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); - ent.origin[2] += 48; + ent.origin[2] += 70; // BFP - BFP puts the floating sprite a bit up. Q3 default: 48 ent.reType = RT_SPRITE; ent.customShader = shader; ent.radius = 10; @@ -1777,10 +1874,30 @@ static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; ent.shaderRGBA[3] = 255; + // BFP - Monster gamemode, player monster' floating sprite is bigger and higher than a normal one + if ( cent->currentState.eFlags & EF_MONSTER ) { + ent.origin[2] += 250; + ent.radius = 50; + } trap_R_AddRefEntityToScene( &ent ); } +/* +=============== +CG_FloatSpriteCheck + +Check if there's some EF flag enabled +=============== +*/ +static qboolean CG_FloatSpriteCheck( centity_t *cent, int eFlag, qhandle_t shader ) { // BFP - Check EF flag and don't show float sprite to the player itself + if ( cent->currentState.eFlags & eFlag ) { + CG_PlayerFloatSprite( cent, shader ); + return qtrue; + } + return qfalse; +} + /* =============== @@ -1792,54 +1909,110 @@ Float sprites over the player's head static void CG_PlayerSprites( centity_t *cent ) { int team; - if ( cent->currentState.eFlags & EF_CONNECTION ) { - CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); - return; - } + if ( CG_FloatSpriteCheck( cent, EF_CONNECTION, cgs.media.connectionShader ) ) return; + if ( CG_FloatSpriteCheck( cent, EF_TALK, cgs.media.balloonShader ) ) return; + if ( CG_FloatSpriteCheck( cent, EF_AWARD_EXCELLENT, cgs.media.medalExcellent ) ) return; + // BFP - No impressive, gauntlet, defend, assist and cap medals +#if 0 + if ( CG_FloatSpriteCheck( cent, EF_AWARD_IMPRESSIVE, cgs.media.medalImpressive ) ) return; + if ( CG_FloatSpriteCheck( cent, EF_AWARD_GAUNTLET, cgs.media.medalGauntlet ) ) return; + if ( CG_FloatSpriteCheck( cent, EF_AWARD_DEFEND, cgs.media.medalDefend ) ) return; + if ( CG_FloatSpriteCheck( cent, EF_AWARD_ASSIST, cgs.media.medalAssist ) ) return; + if ( CG_FloatSpriteCheck( cent, EF_AWARD_CAP, cgs.media.medalCapture ) ) return; +#endif - if ( cent->currentState.eFlags & EF_TALK ) { - CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + if ( !(cent->currentState.eFlags & EF_DEAD) && + cg.snap->ps.persistant[PERS_TEAM] == team && + cgs.gametype >= GT_TEAM) { + // BFP - BFP doesn't use cg_drawFriend to draw that floating friend sprite, keeps enabled always. + // Just wonder if it was some kind of logic reason and looks like that cvar to be removed was also forgotten + // if (cg_drawFriend.integer) { + CG_PlayerFloatSprite( cent, cgs.media.friendShader ); + // } return; } +} - if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { - CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); - return; - } +/* +=============== +CG_ChargeSmokeBubbles - if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { - CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); - return; - } +Spawns charge smoke and bubble particles when being near something solid or water +=============== +*/ +static void CG_ChargeSmokeBubbles( centity_t *cent, vec3_t mins, vec3_t maxs, + float chargeSmokeSize, float chargeSmokeRadialVel, + float chargeSmokeBaseRadius, float chargeSmokeUpOrigin, + float bubbleSize, float bubbleRange ) { // BFP - Charging smoke and bubble particles + int distance = 300; + vec3_t end; + trace_t trace; + vec3_t chargeSmokePos; + int waterContents; - if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { - CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); - return; + // BFP - Monster gamemode, increase the distance for the particles if the player is the monster + if ( cent->currentState.eFlags & EF_MONSTER ) { + distance = 600; } + + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + end[2] -= distance; - if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { - CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); - return; - } + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); - if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { - CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); - return; + VectorCopy( trace.endpos, chargeSmokePos ); + chargeSmokePos[2] += chargeSmokeUpOrigin; // put a bit above + + waterContents = CG_PointContents( trace.endpos, -1 ); // detect if the player is entirely under water + if ( !( cent->currentState.eFlags & EF_KI_BOOST ) + && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_CHARGE + && !( waterContents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) +#if 0 /* if the player isn't moving */ + && !cent->currentState.pos.trDelta[0] + && !cent->currentState.pos.trDelta[1] + && !cent->currentState.pos.trDelta[2] +#endif + && ( trace.fraction < 1.0f || cent->currentState.groundEntityNum != ENTITYNUM_NONE ) ) { + // if stepping a mover + if ( !( trace.fraction <= 0.75f ) + && cent->currentState.groundEntityNum != ENTITYNUM_NONE ) { + chargeSmokePos[2] += 280; + // BFP - Monster gamemode, increase the charge smoke pos distance if the player is the monster + if ( cent->currentState.eFlags & EF_MONSTER ) { + chargeSmokePos[2] += 230; + } + } + CG_ParticleChargeSmoke( cent, cgs.media.particleSmokeShader, chargeSmokePos, chargeSmokeSize, chargeSmokeRadialVel, chargeSmokeBaseRadius ); } - if ( cent->currentState.eFlags & EF_AWARD_CAP ) { - CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); + // don't spawn other bubble particles if already underwater + waterContents = CG_PointContents( cent->lerpOrigin, -1 ); + if ( waterContents & CONTENTS_WATER ) { return; } - team = cgs.clientinfo[ cent->currentState.clientNum ].team; - if ( !(cent->currentState.eFlags & EF_DEAD) && - cg.snap->ps.persistant[PERS_TEAM] == team && - cgs.gametype >= GT_TEAM) { - if (cg_drawFriend.integer) { - CG_PlayerFloatSprite( cent, cgs.media.friendShader ); + // water surface + if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_RUN + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_BACK + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYA + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYB + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_CHARGE ) { // apply on ki charging status too + trace_t waterTrace; + + // tracing the water surface + trap_CM_BoxTrace( &waterTrace, cent->lerpOrigin, end, mins, maxs, 0, CONTENTS_WATER ); + + waterTrace.endpos[2] -= 8; // put a bit down to make the bubbles move + if ( cent->currentState.eFlags & EF_MONSTER ) { // BFP - Monster gamemode, put a bit more down the bubbles + waterTrace.endpos[2] -= 20; + } + if ( waterTrace.fraction >= 0 && waterTrace.fraction <= 0.70f ) { + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, waterTrace.endpos, end, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, waterTrace.endpos, end, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, waterTrace.endpos, end, 700, bubbleRange, bubbleSize ); } - return; } } @@ -1852,28 +2025,176 @@ Returns the Z component of the surface being shadowed should it return a full plane instead of a Z? =============== */ -#define SHADOW_DISTANCE 128 static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { + // BFP - Shadow distance variable isn't constant anymore + int shadow_distance = 128; vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; trace_t trace; float alpha; + int contents, waterContents; // BFP - To detect if there is water or lava + float radius = 24.0f; // BFP - Shadow radius + + // BFP - Bubble particle size and range + float bubbleSize = 2; + float bubbleRange = 10; + + // BFP - Dash smoke particle size, velocity and acceleration (dispersion) + float dashSmokeSize = 25; + float dashSmokeVelDisp = 401; + float dashSmokeUpVel = 20; + float dashSmokeAccel = 10; + + // BFP - Antigrav rock particle size, spawn range and end time + float antigravRockSize = 2; + float antigravRockSpawnRange = 50; + float antigravRockEndTime = 450; + + // BFP - Charge smoke particle size, radial velocity, base radius and up origin + float chargeSmokeSize = 40; + float chargeSmokeRadialVel = 450; + float chargeSmokeBaseRadius = 80; + float chargeSmokeUpOrigin = 20; + + // BFP - Monster gamemode, resize shadow and particles if the player is the monster + if ( cent->currentState.eFlags & EF_MONSTER ) { + shadow_distance = 400; + mins[0] *= 2.5; + mins[1] *= 2.5; + + maxs[0] *= 2.5; + maxs[1] *= 2.5; + radius = 180; + bubbleSize = 8; + bubbleRange = 150; + + dashSmokeSize = 120; + dashSmokeVelDisp = 1201; + dashSmokeUpVel = 1400; + dashSmokeAccel = 260; + + antigravRockSize = 8; + antigravRockSpawnRange = 220; + antigravRockEndTime = 2050; + + chargeSmokeSize = 150; + chargeSmokeRadialVel = 760; + chargeSmokeBaseRadius = 300; + chargeSmokeUpOrigin = 50; + } *shadowPlane = 0; + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + end[2] -= shadow_distance; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // BFP - Dash smoke and bubble particles when using ki boost on the ground or above the water + contents = CG_PointContents( trace.endpos, -1 ); + if ( ( cent->currentState.eFlags & EF_AURA ) || ( cent->currentState.eFlags & EF_AURA_TIER_UP ) ) { + if ( ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_RUN + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_BACK + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYA + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYB + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_CHARGE ) // apply on ki charging status too + && ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) + && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + && ( trace.fraction <= 0.70f + // If the player is stepping a mover: + || cent->currentState.groundEntityNum != ENTITYNUM_NONE ) ) ) { + // BFP - Apply dash smoke particle for the trail, if the function were used directly, it would generate too many particles than we expected + vec3_t dashSmokePos; + VectorCopy( trace.endpos, dashSmokePos ); + + // if stepping a mover + if ( !( trace.fraction <= 0.70f ) + && cent->currentState.groundEntityNum != ENTITYNUM_NONE ) { + dashSmokePos[2] += 100; + } + CG_ParticleDashSmoke( cent, cgs.media.particleSmokeShader, dashSmokePos, dashSmokeSize, dashSmokeVelDisp, dashSmokeUpVel, dashSmokeAccel ); + } + + // BFP - Charging smoke and bubble particles + CG_ChargeSmokeBubbles( cent, mins, maxs, + chargeSmokeSize, chargeSmokeRadialVel, + chargeSmokeBaseRadius, chargeSmokeUpOrigin, + bubbleSize, bubbleRange ); + + waterContents = CG_PointContents( cent->lerpOrigin, -1 ); // BFP - Detect if the player is entirely under water + // BFP - Antigrav rock particles on ki charging status + if ( !( cent->currentState.eFlags & EF_KI_BOOST ) + && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_CHARGE + && !( waterContents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) +#if 0 /* if the player isn't moving */ + && !cent->currentState.pos.trDelta[0] + && !cent->currentState.pos.trDelta[1] + && !cent->currentState.pos.trDelta[2] +#endif + && ( trace.fraction <= 0.75f + // If the player is stepping a mover: + || cent->currentState.groundEntityNum != ENTITYNUM_NONE ) + && !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + // BFP - Spawn randomly the antigrav rock shaders with the particles + vec3_t antigravRockPos; + VectorCopy( trace.endpos, antigravRockPos ); + + // if stepping a mover + if ( !( trace.fraction <= 0.75f ) + && cent->currentState.groundEntityNum != ENTITYNUM_NONE ) { + antigravRockPos[2] += 100; + } + + // BFP - Rock models on ki recharge + if ( cg_3dparticles.integer > 0 + && cgs.media.pebbleMdl1 + && cgs.media.pebbleMdl2 + && cgs.media.pebbleMdl3 ) { + int rockModelIndex = rand() % 3; + switch ( rockModelIndex ) { + case 0: { + CG_ParticleAntigravRock( cgs.media.pebbleShader1, cgs.media.pebbleMdl1, cent, cent->currentState.clientNum, antigravRockPos, antigravRockSize, antigravRockSpawnRange, antigravRockEndTime ); + break; + } + case 1: { + CG_ParticleAntigravRock( cgs.media.pebbleShader2, cgs.media.pebbleMdl2, cent, cent->currentState.clientNum, antigravRockPos, antigravRockSize, antigravRockSpawnRange, antigravRockEndTime ); + break; + } + default: { + CG_ParticleAntigravRock( cgs.media.pebbleShader3, cgs.media.pebbleMdl3, cent, cent->currentState.clientNum, antigravRockPos, antigravRockSize, antigravRockSpawnRange, antigravRockEndTime ); + } + } + } else { + int shaderIndex = rand() % 3; + switch ( shaderIndex ) { + case 0: { + CG_ParticleAntigravRock( cgs.media.pebbleShader1, 0, cent, cent->currentState.clientNum, antigravRockPos, antigravRockSize, antigravRockSpawnRange, antigravRockEndTime ); + break; + } + case 1: { + CG_ParticleAntigravRock( cgs.media.pebbleShader2, 0, cent, cent->currentState.clientNum, antigravRockPos, antigravRockSize, antigravRockSpawnRange, antigravRockEndTime ); + break; + } + default: { + CG_ParticleAntigravRock( cgs.media.pebbleShader3, 0, cent, cent->currentState.clientNum, antigravRockPos, antigravRockSize, antigravRockSpawnRange, antigravRockEndTime ); + } + } + } + + } + } + if ( cg_shadows.integer == 0 ) { return qfalse; } + // BFP - No invis powerup +#if 0 // no shadows when invisible if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { return qfalse; } - - // send a trace down from the player to the ground - VectorCopy( cent->lerpOrigin, end ); - end[2] -= SHADOW_DISTANCE; - - trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); +#endif // no shadow if too high if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { @@ -1895,7 +2216,7 @@ static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { // add the mark as a temporary, so it goes directly to the renderer // without taking a spot in the cg_marks array CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, - cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); + cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, radius, qtrue ); return qtrue; } @@ -1913,6 +2234,7 @@ static void CG_PlayerSplash( centity_t *cent ) { trace_t trace; int contents; polyVert_t verts[4]; + float markSize = 32; if ( !cg_shadows.integer ) { return; @@ -1921,6 +2243,12 @@ static void CG_PlayerSplash( centity_t *cent ) { VectorCopy( cent->lerpOrigin, end ); end[2] -= 24; + // BFP - Monster gamemode, player monster water surface mark size and position + if ( cent->currentState.eFlags & EF_MONSTER ) { + markSize = 192; + end[2] -= 114; + } + // 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 ); @@ -1931,6 +2259,11 @@ static void CG_PlayerSplash( centity_t *cent ) { VectorCopy( cent->lerpOrigin, start ); start[2] += 32; + // BFP - Monster gamemode, player monster water surface mark position + if ( cent->currentState.eFlags & EF_MONSTER ) { + start[2] += 164; + } + // 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 ) ) { @@ -1946,50 +2279,238 @@ static void CG_PlayerSplash( centity_t *cent ) { // 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; + verts[0].xyz[0] -= markSize; + verts[0].xyz[1] -= markSize; + Vector2Set( verts[0].st, 0, 0 ); + Byte4Set( verts[0].modulate, 255, 255, 255, 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; + verts[1].xyz[0] -= markSize; + verts[1].xyz[1] += markSize; + Vector2Set( verts[1].st, 0, 1 ); + Byte4Set( verts[1].modulate, 255, 255, 255, 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; + verts[2].xyz[0] += markSize; + verts[2].xyz[1] += markSize; + Vector2Set( verts[2].st, 1, 1 ); + Byte4Set( verts[2].modulate, 255, 255, 255, 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; + verts[3].xyz[0] += markSize; + verts[3].xyz[1] -= markSize; + Vector2Set( verts[3].st, 1, 0 ); + Byte4Set( verts[3].modulate, 255, 255, 255, 255 ); trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); } +/* +=============== +CG_ModelSize + +Change/scale model size +=============== +*/ +void CG_ModelSize( refEntity_t *model, float size ) { // BFP - Model size + model->axis[0][0] *= size; + model->axis[0][1] *= size; + model->axis[0][2] *= size; + + model->axis[1][0] *= size; + model->axis[1][1] *= size; + model->axis[1][2] *= size; + + model->axis[2][0] *= size; + model->axis[2][1] *= size; + model->axis[2][2] *= size; +} + +/* +========================== +CG_AuraPowerlevelSetShaderColor + +Powerlevel is divided into tiers. +While most powerlevel effects are based on the powerlevel itself, +aura color is determined by the tier. + +- Tier 1: < 100,000 PL Blue aura +- Tier 2: 100,000 – 250,000 PL Red aura +- Tier 3: 250,000 – 500,000 PL Red aura +- Tier 4: 500,000 – 999,000 PL Red aura +- Ultimate Tier: 1 mil PL Yellow aura +========================== +*/ +static qhandle_t CG_AuraPowerlevelSetShaderColor( entityState_t *state ) { + qhandle_t auraShader = cgs.media.auraRedTinyShader; + int powerlevel = state->frame; + + if ( state->clientNum == cg.snap->ps.clientNum ) { // fixes a weird bug when trying to see the powerlevel of itself + powerlevel = cg.snap->ps.persistant[PERS_POWERLEVEL]; + } + + // red + if ( cg_lightweightAuras.integer <= 0 + && cg_polygonAura.integer <= 0 + && cg_spriteAura.integer <= 0 + && cg_particleAura.integer <= 0 ) { + auraShader = cgs.media.auraRedChargeShader; + if ( ( state->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + && !( state->eFlags & EF_AURA_TIER_UP ) ) { + auraShader = cgs.media.auraRedUseShader; + } + } + // blue + if ( powerlevel < 100 + || ( cgs.gametype >= GT_TEAM && cgs.clientinfo[ state->clientNum ].team == TEAM_BLUE ) ) { + auraShader = cgs.media.auraBlueTinyShader; + + if ( cg_lightweightAuras.integer <= 0 + && cg_polygonAura.integer <= 0 + && cg_spriteAura.integer <= 0 + && cg_particleAura.integer <= 0 ) { + auraShader = cgs.media.auraBlueChargeShader; + if ( ( state->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + && !( state->eFlags & EF_AURA_TIER_UP ) ) { + auraShader = cgs.media.auraBlueUseShader; + } + } + } + // yellow + if ( powerlevel >= 1000 + && !( cgs.gametype >= GT_TEAM ) ) { + auraShader = cgs.media.auraYellowTinyShader; + + if ( cg_lightweightAuras.integer <= 0 + && cg_polygonAura.integer <= 0 + && cg_spriteAura.integer <= 0 + && cg_particleAura.integer <= 0 ) { + auraShader = cgs.media.auraYellowChargeShader; + if ( ( state->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + && !( state->eFlags & EF_AURA_TIER_UP ) ) { + auraShader = cgs.media.auraYellowUseShader; + } + } + } + return auraShader; +} + +/* +=============== +CG_AuraAnims + +Handle aura animations, when idling it sets the aura vertical rotation, so the aura rotates vertically +=============== +*/ +static void CG_AuraAnims( centity_t *cent, refEntity_t *aura, qboolean reversed, vec3_t auraInverseRotation ) { // BFP - Aura animations (change model shaders) + if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_RUN + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMP ) { + aura->hModel = cgs.media.runauraModel; + } else if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_BACK + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMPB + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYB ) { + aura->hModel = cgs.media.backauraModel; + } else if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYA ) { + aura->hModel = cgs.media.flyauraModel; + } else { + aura->hModel = cgs.media.auraModel; + if ( reversed ) { + VectorNegate( cg.autoAngles, auraInverseRotation ); + AnglesToAxis( auraInverseRotation, aura->axis ); + } else { + AnglesToAxis( cg.autoAngles, aura->axis ); + } + } +} + + +/* +=============== +CG_DynamicAuraLight + +Dynamic aura light, note: when charging it changes the shinning a bit +Aura lights like cg_smallOwnAura only can be shown to itself and not the other clients, +the other clients only show small lights. +=============== +*/ +static void CG_DynamicAuraLight( centity_t *cent, int clientNum, float r, float g, float b ) { // BFP - Dynamic aura light + int dLightSize = 200; + int rndDLight = dLightSize * 0.7845; + int firstRndDlight = dLightSize * 1.26; + + // BFP - Monster gamemode, player monster dynamic light size + if ( cent->currentState.eFlags & EF_MONSTER ) { + dLightSize = 1000; + } + // BFP - NOTE: Originally, if cg_spriteAura or cg_particleAura is on, the lights aren't displayed. + // But in that case, that can displayed, so it makes no sense not being displayed and + // maybe these things were broken on original BFP. + if ( cg_lightAuras.integer > 0 ) { + if ( clientNum == cg.snap->ps.clientNum && cg_smallOwnAura.integer > 0 ) { + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize, r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize, r, g, b ); + if ( !( cg.predictedPlayerState.pm_flags & PMF_KI_CHARGE ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&rndDLight), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&rndDLight), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&rndDLight), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&rndDLight), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&rndDLight), r, g, b ); + } else { + dLightSize = 100; + // BFP - Monster gamemode, player monster dynamic light size + if ( cent->currentState.eFlags & EF_MONSTER ) { + dLightSize = 500; + } + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&dLightSize), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&dLightSize), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&dLightSize), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&dLightSize), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&dLightSize), r, g, b ); + } + } else if ( clientNum != cg.snap->ps.clientNum || cg_lightweightAuras.integer > 0 || cg_polygonAura.integer > 0 || cg_highPolyAura.integer > 0 ) { + dLightSize = 50; + // BFP - Monster gamemode, player monster dynamic light size + if ( cent->currentState.eFlags & EF_MONSTER ) { + dLightSize = 250; + } + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&(dLightSize * 2)), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&(dLightSize * 2)), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&(dLightSize * 2)), r, g, b ); + } else { + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&firstRndDlight), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&rndDLight), r, g, b ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&rndDLight), r, g, b ); + if ( !( cg.predictedPlayerState.pm_flags & PMF_KI_CHARGE ) ) { + dLightSize = 100; + // BFP - Monster gamemode, player monster dynamic light size + if ( cent->currentState.eFlags & EF_MONSTER ) { + dLightSize = 500; + } + firstRndDlight = dLightSize * 1.5; + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&firstRndDlight), r, g, b ); + } + } + } +} + + +/* +=============== +CG_RemoveKiTrails + +Handle aura animations, when idling it sets the aura vertical rotation, so the aura rotates vertically +=============== +*/ +static void CG_RemoveKiTrails( centity_t *cent, int clientNum, vec3_t kiTrailOrigin, qhandle_t kiTrailShader, qboolean fastRemove ) { // BFP - Remove ki trails + if ( cg.time > cent->pe.kiTrailTime ) { // reset ki trail position avoid being zeroed + CG_ResetTrail( KI_TRAIL, clientNum, kiTrailOrigin ); + } else { // ki trails keep running in that moment, but their segments are being removed + CG_KiTrail( clientNum, kiTrailOrigin, fastRemove, kiTrailShader ); + } +} + /* =============== CG_AddRefEntityWithPowerups @@ -1998,45 +2519,316 @@ Adds a piece with modifications or duplications for powerups Also called by CG_Missile for quad rockets, but nobody can tell... =============== */ -void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { +void CG_AddRefEntityWithPowerups( refEntity_t ent, entityState_t *state, int team ) { + // BFP - Powerlevel + int powerlevel = state->frame; + + if ( state->clientNum == cg.snap->ps.clientNum ) { // fixes a weird bug when trying to see the powerlevel of itself + powerlevel = cg.snap->ps.persistant[PERS_POWERLEVEL]; + } + // BFP - No invis powerup +#if 0 if ( state->powerups & ( 1 << PW_INVIS ) ) { - ent->customShader = cgs.media.invisShader; - trap_R_AddRefEntityToScene( ent ); - } else { - /* - if ( state->eFlags & EF_KAMIKAZE ) { - if (team == TEAM_BLUE) - ent->customShader = cgs.media.blueKamikazeShader; - else - ent->customShader = cgs.media.redKamikazeShader; - trap_R_AddRefEntityToScene( ent ); + ent.customShader = cgs.media.invisShader; + trap_R_AddRefEntityToScene( &ent ); + return; + } +#endif + + // render main model + trap_R_AddRefEntityToScene( &ent ); + + // BFP - Render ultimate perma-glow when already transformed + if ( cg_permaglowUltimate.integer > 0 + && powerlevel >= 1000 ) { + ent.customShader = cgs.media.ultimateAuraShader; + if ( ent.customShader ) { + trap_R_AddRefEntityToScene( &ent ); + } + } + ent.customShader = CG_AuraPowerlevelSetShaderColor( state ); + + if ( ( state->eFlags & EF_AURA ) || ( state->eFlags & EF_AURA_TIER_UP ) ) { + // BFP - Transformation aura + if ( ( state->eFlags & EF_AURA_TIER_UP ) && cg_transformationAura.integer <= 0 ) { + return; + } + // BFP - If the player is using lightweight auras or their own small aura + if ( ( cg_lightweightAuras.integer > 0 + || ( state->clientNum == cg.snap->ps.clientNum + && cg_smallOwnAura.integer > 0 ) ) + && cg_spriteAura.integer <= 0 + && cg_particleAura.integer <= 0 ) { + trap_R_AddRefEntityToScene( &ent ); + } + + // BFP - Resize shader aura when tier is up + if ( state->eFlags & EF_AURA_TIER_UP ) { + CG_ModelSize( &ent, 1.4f ); + } + + // BFP - Shader aura + if ( cg_lightweightAuras.integer <= 0 + && cg_polygonAura.integer <= 0 + && cg_spriteAura.integer <= 0 + && cg_particleAura.integer <= 0 ) { + if ( state->clientNum == cg.snap->ps.clientNum + && cg_smallOwnAura.integer > 0 ) { + trap_R_AddRefEntityToScene( &ent ); + return; + } + ent.customShader = CG_AuraPowerlevelSetShaderColor( state ); + trap_R_AddRefEntityToScene( &ent ); + } + } +} + +/* +============ +CG_SpriteAura + +Adds sprite aura, just one quad +============ +*/ +static void CG_SpriteAura( refEntity_t aura ) { // BFP - Sprite aura + // BFP - NOTE: What shader was added?? Originally, BFP didn't finish the shader to attach or they forgot... + // That radius looks a bit big for an aura, maybe they thought to fit the texture that way or some circular aura? + // And... What the heck? This sprite view depends of pitch angle until some client connects? + // Also when cg_smallOwnAura cvar is enabled, it doesn't display any aura to the client itself. + // Moreover, the lights are disabled as mentioned previously in CG_DynamicAuraLight function comments + // In the future, the shader should be added, not sure what kind of aura is this... + float pitchView = cg.refdefViewAngles[PITCH]; + int i, connectedClients = 1; + + for ( i = 0; i < MAX_CLIENTS; ++i ) { + if ( cg_entities[i].currentValid ) { + ++connectedClients; + } + } + aura.reType = RT_SPRITE; + aura.customShader = 0; + aura.radius += 75; + if ( connectedClients > 1 ) { + pitchView = -15; + } + aura.rotation = pitchView; + + aura.shaderRGBA[0] = 255; + aura.shaderRGBA[1] = 255; + aura.shaderRGBA[2] = 255; + aura.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &aura ); +} + + +/* +============ +CG_Aura + +Adds aura and ki trails +============ +*/ +static void CG_Aura( centity_t *cent, int clientNum, clientInfo_t *ci, int renderfx, refEntity_t legs, qhandle_t kiTrailShader ) { // BFP - Aura and ki trails + refEntity_t aura; + refEntity_t aura2; // secondary aura + vec3_t auraInverseRotation; // for aura inverse rotation + vec3_t kiTrailOrigin; + int powerlevel = cent->currentState.frame; + const int KI_TRAIL_ZPOS = 5; + + if ( clientNum == cg.snap->ps.clientNum ) { // fixes a weird bug when trying to see the powerlevel of itself + powerlevel = cg.snap->ps.persistant[PERS_POWERLEVEL]; + } + + memset( &aura, 0, sizeof(aura) ); + memset( &aura2, 0, sizeof(aura2) ); + + // origin setup for ki trails + VectorCopy( cent->lerpOrigin, kiTrailOrigin ); + kiTrailOrigin[2] += KI_TRAIL_ZPOS; + + if ( ( cent->currentState.eFlags & EF_AURA ) || ( cent->currentState.eFlags & EF_AURA_TIER_UP ) ) { + // trace for bubble particles only when moving in the water and charging + int destContentType = CG_PointContents( legs.origin, -1 ); + + // BFP - Ki trail + if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_CHARGE + && cg_kiTrail.integer > 0 ) { + // apply time for using ki trail + cent->pe.kiTrailTime = cg.time + cg_kiTrail.integer*7; + + CG_KiTrail( clientNum, kiTrailOrigin, qfalse, kiTrailShader ); + } else { // handle when the ki trail was being used previously + CG_RemoveKiTrails( cent, clientNum, kiTrailOrigin, kiTrailShader, qtrue ); + } + + // spawning bubble particles + if ( destContentType & CONTENTS_WATER ) { + trace_t trace; + vec3_t start, bubbleOrigin; + float bubbleSize = 2; + float bubbleRange = 10; + + VectorCopy( legs.origin, bubbleOrigin ); + trap_CM_BoxTrace( &trace, start, bubbleOrigin, NULL, NULL, 0, CONTENTS_WATER ); + + bubbleOrigin[2] += -17; // put the origin below the character's feet + + // BFP - Monster gamemode, player monster bubble particle size, range and position + if ( cent->currentState.eFlags & EF_MONSTER ) { + bubbleSize = 8; + bubbleRange = 100; + bubbleOrigin[2] += -85; // put the origin below the character's feet + trace.endpos[2] += 100; + } + + if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYA + || ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_FLYB ) { + bubbleOrigin[2] += 6; // put the origin near the player origin point + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 700, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 700, bubbleRange, bubbleSize ); + } else if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_CHARGE ) { + bubbleOrigin[2] += -3; // put the origin a little below + bubbleRange *= 2; + + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 0, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 0, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 0, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 0, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 0, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 0, bubbleRange, bubbleSize ); + CG_ParticleBubble( cent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, bubbleOrigin, trace.endpos, 0, bubbleRange, bubbleSize ); + } + } + + // apply the render type + aura.reType = aura2.reType = RT_MODEL; + + // clear the axis to keep the position + AxisClear( aura.axis ); + AxisClear( aura2.axis ); + + // if the player is moving like going forward and backwards, then use other aura model + CG_AuraAnims( cent, &aura, 0, auraInverseRotation ); + CG_AuraAnims( cent, &aura2, 1, auraInverseRotation ); + + // resize the aura + CG_ModelSize( &aura, 1.3f ); + CG_ModelSize( &aura2, 1.5f ); + + // set aura position to the player + VectorCopy( legs.origin, aura.origin ); + VectorCopy( legs.lightingOrigin, aura.lightingOrigin ); + VectorCopy( legs.origin, aura2.origin ); + VectorCopy( legs.lightingOrigin, aura2.lightingOrigin ); + + // apply light blinking + aura.customShader = aura2.customShader = CG_AuraPowerlevelSetShaderColor( ¢->currentState ); + // blue + if ( powerlevel < 100 + || ( cgs.gametype >= GT_TEAM && ci->team == TEAM_BLUE ) ) { + CG_DynamicAuraLight( cent, clientNum, 0.2f, 0.2f, 1.0 ); + } + // yellow + else if ( powerlevel >= 1000 + && !( cgs.gametype >= GT_TEAM ) ) { + //CG_DynamicAuraLight( cent, clientNum, 1.0, 1.0, 0 ); + CG_DynamicAuraLight( cent, clientNum, 1.0, 1.0, 0.2f ); + } + // red + else { + CG_DynamicAuraLight( cent, clientNum, 1.0, 0.2f, 0.2f ); + } + + aura.renderfx = aura2.renderfx = renderfx; + VectorCopy( aura.origin, aura.oldorigin ); // don't positionally lerp at all + VectorCopy( aura2.origin, aura2.oldorigin ); // don't positionally lerp at all + + // ki boost and ki charge sounds + if ( !( cent->currentState.eFlags & EF_AURA_TIER_UP ) ) { + if ( !( cent->currentState.eFlags & EF_KI_BOOST ) + && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_CHARGE ) { + trap_S_AddLoopingSound( cent->currentState.clientNum, cent->lerpOrigin, + vec3_origin, cgs.media.kiChargeSound ); + } else { + trap_S_AddLoopingSound( cent->currentState.clientNum, cent->lerpOrigin, + vec3_origin, cgs.media.kiUseSound ); + } + } + + // aura tier + if ( cent->currentState.eFlags & EF_AURA_TIER_UP ) { + // resize the aura + CG_ModelSize( &aura, 1.3f ); + CG_ModelSize( &aura2, 1.5f ); + } + + // keep the aura pivot tagged in tag_torso + CG_PositionRotatedEntityOnTag( &aura, &legs, legs.hModel /*ci->legsModel*/, "tag_torso" ); + CG_PositionRotatedEntityOnTag( &aura2, &legs, legs.hModel /*ci->legsModel*/, "tag_torso" ); + + // BFP - Sprite aura + if ( ( cg_spriteAura.integer > 0 && cg_smallOwnAura.integer <= 0 ) + || ( cg_spriteAura.integer > 0 && cg_smallOwnAura.integer > 0 && clientNum != cg.snap->ps.clientNum ) ) { + // BFP - Monster gamemode, player monster sprite aura is bigger + if ( cent->currentState.eFlags & EF_MONSTER ) { + aura.radius = 325; + } + CG_SpriteAura( aura ); + return; } - else {*/ - trap_R_AddRefEntityToScene( ent ); - //} - if ( state->powerups & ( 1 << PW_QUAD ) ) - { - if (team == TEAM_RED) - ent->customShader = cgs.media.redQuadShader; - else - ent->customShader = cgs.media.quadShader; - trap_R_AddRefEntityToScene( ent ); + // BFP - Particle aura + if ( ( cg_particleAura.integer > 0 && cg_smallOwnAura.integer <= 0 ) + || ( cg_particleAura.integer > 0 && cg_smallOwnAura.integer > 0 && clientNum != cg.snap->ps.clientNum ) ) { + // BFP - NOTE: Particle aura wasn't fully implemented on original BFP. + // Originally, particle aura wasn't correctly placed on player's origin, it was zeroed and + // when the player moves up, the aura was moving to right, and when moves down, it was moving to left; + // moreover, spawns too many particles; also the shader uses bubble ones and the particle size is a bit big. + // It's unknown what they planned in their future. + // But this time, it's placed to player's origin like when being underwater, more fading is added, + // also it doesn't spawn too many particles. + // In the future, that should be tweaked, bubble shader doesn't seem to fit well. + vec3_t pAuraOrigin; + VectorCopy( legs.origin, pAuraOrigin ); + + pAuraOrigin[2] += -18; // put the origin a little below + + CG_ParticleAura( cent, clientNum, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, pAuraOrigin, NULL, 20 ); + CG_ParticleAura( cent, clientNum, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, pAuraOrigin, NULL, 20 ); + return; } - if ( state->powerups & ( 1 << PW_REGEN ) ) { - if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { - ent->customShader = cgs.media.regenShader; - trap_R_AddRefEntityToScene( ent ); + + // BFP - Small own aura only can be shown to the one who enables it for themself, not everyone + if ( clientNum != cg.snap->ps.clientNum || cg_smallOwnAura.integer <= 0 ) { + // BFP - Transformation aura + if ( ( cent->currentState.eFlags & EF_AURA_TIER_UP ) && cg_transformationAura.integer <= 0 ) { + return; + } + + // add aura + if ( cg_spriteAura.integer <= 0 && cg_particleAura.integer <= 0 + && cg_polygonAura.integer > 0 && cg_lightweightAuras.integer <= 0 ) { + trap_R_AddRefEntityToScene( &aura ); + } + + // add secondary aura to make look cooler, a bit bigger than the other + if ( cg_spriteAura.integer <= 0 && cg_particleAura.integer <= 0 + && cg_polygonAura.integer > 0 && cg_highPolyAura.integer > 0 && cg_lightweightAuras.integer <= 0 ) { + trap_R_AddRefEntityToScene( &aura2 ); } } - if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { - ent->customShader = cgs.media.battleSuitShader; - trap_R_AddRefEntityToScene( ent ); + } else { + // BFP - Ki trail being removed + if ( cg_kiTrail.integer > 0 ) { + CG_RemoveKiTrails( cent, clientNum, kiTrailOrigin, kiTrailShader, qtrue ); } } } + /* ================= CG_LightVerts @@ -2055,10 +2847,7 @@ int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) for (i = 0; i < numVerts; i++) { incoming = DotProduct (normal, lightDir); if ( incoming <= 0 ) { - verts[i].modulate[0] = ambientLight[0]; - verts[i].modulate[1] = ambientLight[1]; - verts[i].modulate[2] = ambientLight[2]; - verts[i].modulate[3] = 255; + Byte4Set( verts[i].modulate, ambientLight[0], ambientLight[1], ambientLight[2], 255 ); continue; } j = ( ambientLight[0] + incoming * directedLight[0] ); @@ -2084,6 +2873,36 @@ int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) return qtrue; } + +/* +=============== +CG_FindAttachMuzzleTag +=============== +*/ +static void CG_FindAttachMuzzleTag( centity_t *cent, clientInfo_t *ci, refEntity_t *torso, refEntity_t *legs, refEntity_t *head, char *attackTagPart, char *attackTagName ) { // BFP - Finds the tag to attach the muzzle + orientation_t tagOrient; + + // legs + if ( attackTagPart && attackTagPart[0] && !Q_stricmp( attackTagPart, "legs" ) + && trap_R_LerpTag( &tagOrient, legs->hModel, legs->oldframe, legs->frame, 1.0 - legs->backlerp, attackTagName ) ) { + CG_AddPlayerWeapon( legs, NULL, cent, ci->team, attackTagName ); + return; + } + + // torso + if ( attackTagPart && attackTagPart[0] && !Q_stricmp( attackTagPart, "torso" ) + && trap_R_LerpTag( &tagOrient, torso->hModel, torso->oldframe, torso->frame, 1.0 - torso->backlerp, attackTagName ) ) { + CG_AddPlayerWeapon( torso, NULL, cent, ci->team, attackTagName ); + return; + } + + // head + if ( attackTagPart && attackTagPart[0] && !Q_stricmp( attackTagPart, "head" ) + && trap_R_LerpTag( &tagOrient, head->hModel, head->oldframe, head->frame, 1.0 - head->backlerp, attackTagName ) ) { + CG_AddPlayerWeapon( head, NULL, cent, ci->team, attackTagName ); + } +} + /* =============== CG_Player @@ -2094,15 +2913,25 @@ void CG_Player( centity_t *cent ) { refEntity_t legs; refEntity_t torso; refEntity_t head; - refEntity_t aura; // BFP - Aura int clientNum; int renderfx; qboolean shadow; float shadowPlane; - // BFP - Aura vertical rotation variables - float auraRotation; - float sinAura, cosAura; - float tmp0, tmp1; + qhandle_t kiTrailShader; + // BFP - Powerlevel for the aura + int powerlevel = -1; + // BFP - Save head for first person vis mode + refEntity_t savedHead; + // BFP - Monster gamemode with g_monster enabled, temporarily swap animations + animation_t backupAnimations[MAX_TOTALANIMATIONS]; + + // BFP - Weapon info to extract + weaponInfo_t *weap = &cg_weapons[ cent->currentState.weapon ]; + + // BFP - That macro makes all body being transformed during tier up. + // Enabled by default, originally on BFP only shows the head as transformation transition + // while the body is already transformed. +#define TIER_UP_TRANSFORM_ENTIRE_BODY 1 // the client number is stored in clientNum. It can't be derived // from the entity number, because a single client may have @@ -2112,6 +2941,26 @@ void CG_Player( centity_t *cent ) { CG_Error( "Bad clientNum on player entity"); } ci = &cgs.clientinfo[ clientNum ]; + + // BFP - Powerlevel for the aura + powerlevel = cent->currentState.frame; + if ( clientNum == cg.snap->ps.clientNum ) { // fixes a weird bug when trying to see the powerlevel of itself + powerlevel = cg.snap->ps.persistant[PERS_POWERLEVEL]; + } + + // BFP - Ki trail shader set + // red + kiTrailShader = cgs.media.kiTrailRedShader; + // blue + if ( powerlevel < 100 + || ( cgs.gametype >= GT_TEAM && ci->team == TEAM_BLUE ) ) { + kiTrailShader = cgs.media.kiTrailBlueShader; + } + // yellow + if ( powerlevel >= 1000 + && !( cgs.gametype >= GT_TEAM ) ) { + kiTrailShader = cgs.media.kiTrailYellowShader; + } // it is possible to see corpses from disconnected players that may // not have valid clientinfo @@ -2121,7 +2970,7 @@ void CG_Player( centity_t *cent ) { // get the player model information renderfx = 0; - if ( cent->currentState.number == cg.snap->ps.clientNum) { + if ( clientNum == cg.snap->ps.clientNum ) { if (!cg.renderingThirdPerson) { renderfx = RF_THIRD_PERSON; // only draw in mirrors } /*else { // BFP - cg_cameraMode cvar doesn't exist @@ -2135,7 +2984,14 @@ void CG_Player( centity_t *cent ) { memset( &legs, 0, sizeof(legs) ); memset( &torso, 0, sizeof(torso) ); memset( &head, 0, sizeof(head) ); - memset( &aura, 0, sizeof(aura) ); // BFP - Aura + + // BFP - Monster gamemode with g_monster enabled, temporarily swap animations + if ( cgs.gametype == GT_MONSTER && cgs.monster > 0 + && ( cent->currentState.eFlags & EF_MONSTER ) + && cgs.media.monsterAnimations[0].numFrames != 0 ) { + memcpy( backupAnimations, ci->animations, sizeof(ci->animations) ); + memcpy( ci->animations, cgs.media.monsterAnimations, sizeof(ci->animations) ); + } // get the rotation information CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); @@ -2144,78 +3000,25 @@ void CG_Player( centity_t *cent ) { CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, &torso.oldframe, &torso.frame, &torso.backlerp ); + // BFP - Monster gamemode with g_monster enabled, temporarily swap animations + if ( cgs.gametype == GT_MONSTER && cgs.monster > 0 + && ( cent->currentState.eFlags & EF_MONSTER ) + && cgs.media.monsterAnimations[0].numFrames != 0 ) { + memcpy( ci->animations, backupAnimations, sizeof(ci->animations) ); + } + // add the talk baloon or disconnect icon CG_PlayerSprites( cent ); - // BFP - Aura ki using - if ( cent->currentState.eFlags & EF_AURA ) { - aura.reType = RT_MODEL; - aura.customShader = cgs.media.auraEffectShader; - aura.hModel = cgs.media.auraModel; - - // reset axis model postion - AxisClear( aura.axis ); - - // fixes rotation when player rotates down/up/right/left... even while flying - // according legs position - // cent->lerpAngles is according player base entity position - AnglesToAxis( ¢->pe.legs.pitchAngle, aura.axis ); - - // set aura position to the player - VectorCopy( cent->lerpOrigin, aura.origin ); - - // BFP - when flying, set correctly into these angles - if ( cg.predictedPlayerState.pm_flags & PMF_FLYING ) { - AnglesToAxis( cent->lerpAngles, aura.axis ); - } - - // BFP - TODO: Aura vertical rotation, aura rotates vertically, - // but when player is flying, Z-axis isn't respecting player's position and angles - /* - auraRotation = cg.time * 0.15f * ( M_PI / 180.0f ); - sinAura = sin( auraRotation ); - cosAura = cos( auraRotation ); - tmp0 = aura.axis[0][0]; - tmp1 = aura.axis[0][1]; - - aura.axis[0][0] = cosAura * tmp0 - sinAura * aura.axis[1][0]; - aura.axis[0][1] = cosAura * tmp1 - sinAura * aura.axis[1][1]; - aura.axis[1][0] = sinAura * tmp0 + cosAura * aura.axis[1][0]; - aura.axis[1][1] = sinAura * tmp1 + cosAura * aura.axis[1][1]; - */ - - trap_R_AddRefEntityToScene( &aura ); - - aura.renderfx = renderfx; - - // BFP - TODO: Add secondary aura and with little size - // who knows why, BFP does that as well - //VectorScale( aura.axis[0], 0.9, aura.axis[0] ); - //VectorScale( aura.axis[1], 0.9, aura.axis[1] ); - //VectorScale( aura.axis[2], 0.9, aura.axis[2] ); - - // BFP - TODO: rotate secondary aura in the opposite direction - //trap_R_AddRefEntityToScene( &aura ); // add secondary aura, but it doesn't respect the angles to the player model - // or should do that so? - // CG_AddRefEntityWithPowerups( &aura, ¢->currentState, 0 ); - - // light blinking - trap_R_AddLightToScene( aura.origin, 100 + (rand()&32), 1, 0.01f, 0.002f ); - trap_R_AddLightToScene( cent->lerpOrigin, 80 + (rand()&11), 1, 0.15f, 0.001f ); - - // BFP - Ki boost and ki charge sounds - if ( ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_CHARGE ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, - vec3_origin, cgs.media.kiUseSound ); - } else if ( ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_CHARGE ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, - vec3_origin, cgs.media.kiChargeSound ); - } - } - // add the shadow shadow = CG_PlayerShadow( cent, &shadowPlane ); + // BFP - Handle the antigrav rock particles when the player is charging + CG_AntigravRockHandling( cent ); + + // BFP - Handle particle aura + CG_ParticleAuraHandling( cent ); + // add a water splash if partially in and out of water CG_PlayerSplash( cent ); @@ -2230,15 +3033,63 @@ void CG_Player( centity_t *cent ) { legs.hModel = ci->legsModel; legs.customSkin = ci->legsSkin; + // BFP - Ultimate tier legs model and skin + if ( powerlevel >= 1000 +#if TIER_UP_TRANSFORM_ENTIRE_BODY + && cg.time > cent->pe.ultTierTransformTime - 800 +#endif + ) { + if ( ci->ultTierLegsModel ) { + legs.hModel = ci->ultTierLegsModel; + } + if ( ci->ultTierLegsSkin ) { + legs.customSkin = ci->ultTierLegsSkin; + } + } + + // BFP - Monster gamemode, set legs model and skin + if ( cgs.gametype == GT_MONSTER && cgs.monster > 0 + && ( cent->currentState.eFlags & EF_MONSTER ) ) { + legs.hModel = cgs.media.monsterLegsModel; + legs.customSkin = cgs.media.monsterLegsSkin; + // BFP - Ultimate tier monster legs model and skin + if ( powerlevel >= 1000 && cg.time > cent->pe.ultTierTransformTime - 800 ) { + if ( cgs.media.monsterUltTierLegsModel ) { + legs.hModel = cgs.media.monsterUltTierLegsModel; + } + if ( cgs.media.monsterUltTierLegsSkin ) { + legs.customSkin = cgs.media.monsterUltTierLegsSkin; + } + } + } + + // BFP - Super Deformed (Chibi style) easter egg for the base model (the legs apply all parts of the body) + if ( cg_superdeformed.integer > 0 ) { + CG_ModelSize( &legs, 0.8f ); + } + VectorCopy( cent->lerpOrigin, legs.origin ); VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + // BFP - Monster gamemode, the player monster is bigger than a normal player + if ( cent->currentState.eFlags & EF_MONSTER ) { + // monster model position + if ( cgs.monster > 0 ) { + CG_ModelSize( &legs, 4.75 ); + } else { // tweak a bit because of the hit box upside... huh... :/ + CG_ModelSize( &legs, 7.9 ); + } + // adjust the model a bit up to see the legs correctly + legs.origin[2] -= 22; + } + legs.shadowPlane = shadowPlane; legs.renderfx = renderfx; + legs.nonNormalizedAxes = qtrue; // BFP - Keep legs visible while being near to the camera VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all - CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); + CG_AddRefEntityWithPowerups( legs, ¢->currentState, ci->team ); // if the model failed, allow the default nullmodel to be displayed if (!legs.hModel) { @@ -2249,97 +3100,187 @@ void CG_Player( centity_t *cent ) { // add the torso // torso.hModel = ci->torsoModel; - if (!torso.hModel) { - return; + torso.customSkin = ci->torsoSkin; + + // BFP - Ultimate tier torso model and skin + if ( powerlevel >= 1000 +#if TIER_UP_TRANSFORM_ENTIRE_BODY + && cg.time > cent->pe.ultTierTransformTime - 800 +#endif + ) { + if ( ci->ultTierTorsoModel ) { + torso.hModel = ci->ultTierTorsoModel; + } + if ( ci->ultTierTorsoSkin ) { + torso.customSkin = ci->ultTierTorsoSkin; + } } - torso.customSkin = ci->torsoSkin; + // BFP - Monster gamemode, set torso model and skin + if ( cgs.gametype == GT_MONSTER && cgs.monster > 0 + && ( cent->currentState.eFlags & EF_MONSTER ) ) { + torso.hModel = cgs.media.monsterTorsoModel; + torso.customSkin = cgs.media.monsterTorsoSkin; + // BFP - Ultimate tier monster torso model and skin + if ( powerlevel >= 1000 && cg.time > cent->pe.ultTierTransformTime - 800 ) { + if ( cgs.media.monsterUltTierTorsoModel ) { + torso.hModel = cgs.media.monsterUltTierTorsoModel; + } + if ( cgs.media.monsterUltTierTorsoSkin ) { + torso.customSkin = cgs.media.monsterUltTierTorsoSkin; + } + } + } + + if ( !torso.hModel ) { + return; + } VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); - CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); + CG_PositionRotatedEntityOnTag( &torso, &legs, legs.hModel /*ci->legsModel*/, "tag_torso" ); torso.shadowPlane = shadowPlane; torso.renderfx = renderfx; - CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); + CG_AddRefEntityWithPowerups( torso, ¢->currentState, ci->team ); // // add the head // head.hModel = ci->headModel; + head.customSkin = ci->headSkin; + + // BFP - Ultimate tier head model and skin + if ( powerlevel >= 1000 && cg.time > cent->pe.ultTierTransformTime - 800 ) { + if ( ci->ultTierHeadModel ) { + head.hModel = ci->ultTierHeadModel; + } + if ( ci->ultTierHeadSkin ) { + head.customSkin = ci->ultTierHeadSkin; + } + } + + // BFP - Monster gamemode, set head model and skin + if ( cgs.gametype == GT_MONSTER && cgs.monster > 0 + && ( cent->currentState.eFlags & EF_MONSTER ) ) { + head.hModel = cgs.media.monsterHeadModel; + head.customSkin = cgs.media.monsterHeadSkin; + // BFP - Ultimate tier monster head model and skin + if ( powerlevel >= 1000 && cg.time > cent->pe.ultTierTransformTime - 800 ) { + if ( cgs.media.monsterUltTierHeadModel ) { + head.hModel = cgs.media.monsterUltTierHeadModel; + } + if ( cgs.media.monsterUltTierHeadSkin ) { + head.customSkin = cgs.media.monsterUltTierHeadSkin; + } + } + } + if ( !head.hModel ) { return; } + + // BFP - Make a model changing effect when aura tier is up + if ( ( cent->currentState.eFlags & EF_AURA_TIER_UP ) + && cg.time > cent->pe.ultTierTransformTime ) { + cent->pe.ultTierTransformTime = cg.time + 1100; + } + if ( !( cent->currentState.eFlags & EF_AURA_TIER_UP ) ) { + cent->pe.ultTierTransformTime = 0; + } + if ( cg_yrgolroxor.integer > 0 ) { // BFP - Yrgol Roxor easter egg head.hModel = 0; // 0: no head model display, display pivot only } - // BFP - First person vis mode doesn't have head model to be displayed - if ( cg_drawOwnModel.integer >= 1 && cg_thirdPerson.integer <= 0 - && cent->currentState.number == cg.snap->ps.clientNum - && !( cent->currentState.eFlags & EF_DEAD ) ) { - head.hModel = 2; // 2: no head model display and no pivot display + // BFP - Super Deformed (Chibi style) easter egg for the head model + if ( cg_superdeformed.integer > 0 ) { + CG_ModelSize( &head, 3.0f ); } - head.customSkin = ci->headSkin; VectorCopy( cent->lerpOrigin, head.lightingOrigin ); - CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); + CG_PositionRotatedEntityOnTag( &head, &torso, torso.hModel /*ci->torsoModel*/, "tag_head" ); head.shadowPlane = shadowPlane; head.renderfx = renderfx; - CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); + // BFP - First person vis mode doesn't have head model to be displayed + savedHead = head; + if ( cg_drawOwnModel.integer >= 1 && cg_thirdPerson.integer <= 0 + && clientNum == cg.snap->ps.clientNum + && !( cent->currentState.eFlags & EF_DEAD ) ) { + memset( &head, 0, sizeof(head) ); + } + + CG_AddRefEntityWithPowerups( head, ¢->currentState, ci->team ); + + // BFP - If the entity is a corpse, avoid drawing ki trails to the dead + if ( cent->currentState.eFlags & EF_DEAD ) { + return; + } // - // add the gun / barrel / flash + // BFP - Aura and ki trails // - CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); + CG_Aura( cent, clientNum, ci, renderfx, legs, kiTrailShader ); - // add powerups floating behind the player - CG_PlayerPowerups( cent, &torso ); - - // BFP - Smoke trail when using Ki boost in the ground - if ( cent->currentState.eFlags & EF_AURA ) { - CG_HasteTrail( cent ); + // BFP - Render ultimate perma-glow dynamic lights when already transformed + if ( cg_lightAuras.integer > 0 && cg_permaglowUltimate.integer > 0 && powerlevel >= 1000 ) { + int dLightSize = 50; + // BFP - Monster gamemode, player monster' dynamic lights are bigger than a normal one + if ( cent->currentState.eFlags & EF_MONSTER ) { + dLightSize = 270; + } + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&( dLightSize + 30 )), 1.0, 1.0, 0.2f ); + trap_R_AddLightToScene( cent->lerpOrigin, dLightSize + (rand()&( dLightSize + 30 )), 1.0, 1.0, 0.2f ); } + // BFP - First person camera setup if ( cg_thirdPerson.integer <= 0 - && cent->currentState.number == cg.snap->ps.clientNum ) { // BFP - Avoid every time some player/bot enters in the server and changes the view into the other player - CG_OffsetFirstPersonView( cent, &torso, ci->torsoModel ); + && clientNum == cg.snap->ps.clientNum ) { // BFP - Avoid every time some player/bot enters in the server and changes the view into the other player + static vec3_t deadOriginDrawOwnModel; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) { + // BFP - Set dead origin where the player was alive when First person vis mode is being used + VectorCopy( cg.refdef.vieworg, deadOriginDrawOwnModel ); + CG_PositionRotatedEntityOnTag( &savedHead, &savedHead, savedHead.hModel /*ci->headModel*/, "tag_eyes" ); + CG_OffsetFirstPersonView( cent, &savedHead, savedHead.hModel /*ci->headModel*/ ); + } else if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 + && ( cent->currentState.eFlags & EF_DEAD ) + && cg_drawOwnModel.integer >= 1 ) { // BFP - Death camera only for First person vis + VectorCopy( deadOriginDrawOwnModel, cg.refdef.vieworg ); + cg.refdefViewAngles[YAW] = cg.snap->ps.damageYaw + cg.snap->ps.damagePitch; + } } -} -/* -=============== -CG_GetTagOrientationFromPlayerEntityParentModel -=============== -*/ -qboolean CG_GetTagOrientationFromPlayerEntityParentModel( centity_t *cent, refEntity_t *parent, - qhandle_t parentModel, char *tagName, orientation_t *tagOrient ) { // BFP - Parent model tag orientation, used for first person vis mode - int i; - orientation_t lerped; - vec3_t tempAxis[3]; + // + // add the gun / barrel / flash + // + // CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); - if ( cent->currentState.eType != ET_PLAYER || !tagName[0] ) { - return qfalse; + // BFP - NOTE: Here's where the player gets the muzzle attached from some of the tags (apply that to client cfg side) (tag_left, tag_right, tag_eyes, ...) + + // BFP - Monster gamemode, for the monster feature option, attach the beam to the mouth, just testing + if ( cgs.gametype == GT_MONSTER && cgs.monster > 0 && ( cent->currentState.eFlags & EF_MONSTER ) ) { + CG_FindAttachMuzzleTag( cent, ci, &torso, &legs, &savedHead, "head", "tag_mouth" ); + } else { + if ( cent->currentState.weapon == WP_BFG ) { // BFP - Testing disk muzzle + CG_FindAttachMuzzleTag( cent, ci, &torso, &legs, &savedHead, "torso", "tag_right" ); + } else { + CG_FindAttachMuzzleTag( cent, ci, &torso, &legs, &savedHead, "torso", "tag_left" ); + } } - - // Prepare the destination orientation_t - AxisClear( tagOrient->axis ); - - // Try to find the tag and return its coordinates - if ( trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName ) ) { - VectorCopy( parent->origin, tagOrient->origin ); - for ( i = 0 ; i < 3 ; i++ ) { - VectorMA( tagOrient->origin, lerped.origin[i], parent->axis[i], tagOrient->origin ); - } - MatrixMultiply( tagOrient->axis, lerped.axis, tempAxis ); - MatrixMultiply( tempAxis, parent->axis, tagOrient->axis ); - return qtrue; - } - return qfalse; + + // BFP - Forcefield test + if ( cent->currentState.weapon == WP_SHOTGUN + && cent->pe.chargeAutoFire && weap->noExplosion ) { + CG_ForceFieldEffect( cent, torso.origin, "models/weaphits/sphere_hi.md3", "AGAAttackShader" ); + } + + // add powerups floating behind the player + CG_PlayerPowerups( cent, &torso ); } //===================================================================== diff --git a/source/cgame/cg_playerstate.c b/source/cgame/cg_playerstate.c index 9eb34d2..b59fbda 100644 --- a/source/cgame/cg_playerstate.c +++ b/source/cgame/cg_playerstate.c @@ -37,9 +37,11 @@ If the ammo has gone low enough to generate the warning, play a sound void CG_CheckAmmo( void ) { int i; int total; - int previous; int weapons; + // BFP - Unused variable + // int previous; + // BFP - TODO: Replace to ki // see about how many seconds of ammo we have remaining @@ -204,15 +206,17 @@ void CG_Respawn( void ) { cg.weaponSelect = cg.snap->ps.weapon; } -extern char *eventnames[]; /* ============== CG_CheckPlayerstateEvents ============== */ -void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { - int i; +extern int eventStack; +extern int eventParm2[ MAX_PREDICTED_EVENTS ]; + +static void CG_CheckPlayerstateEvents( const playerState_t *ps, const playerState_t *ops ) { + int i, n; int event; centity_t *cent; @@ -220,10 +224,12 @@ void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { cent = &cg_entities[ ps->clientNum ]; cent->currentState.event = ps->externalEvent; cent->currentState.eventParm = ps->externalEventParm; - CG_EntityEvent( cent, cent->lerpOrigin ); + CG_EntityEvent( cent, cent->lerpOrigin, -1 ); } cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + n = eventStack - MAX_PS_EVENTS; + if ( n < 0 ) n = 0; // go through the predictable events buffer for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { // if we have a new predictable event @@ -233,9 +239,12 @@ void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + if ( event == EV_NONE ) // ignore empty events + continue; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; - CG_EntityEvent( cent, cent->lerpOrigin ); + + CG_EntityEvent( cent, cent->lerpOrigin, eventParm2[ n++ ] ); cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; @@ -250,11 +259,13 @@ CG_CheckChangedPredictableEvents ================== */ void CG_CheckChangedPredictableEvents( playerState_t *ps ) { - int i; + int i, n; int event; centity_t *cent; cent = &cg.predictedPlayerEntity; + n = eventStack - MAX_PS_EVENTS; + if ( n < 0 ) n = 0; for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { // if (i >= cg.eventSequence) { @@ -268,7 +279,7 @@ void CG_CheckChangedPredictableEvents( playerState_t *ps ) { event = ps->events[ i & (MAX_PS_EVENTS-1) ]; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; - CG_EntityEvent( cent, cent->lerpOrigin ); + CG_EntityEvent( cent, cent->lerpOrigin, eventParm2[ n++ ] ); cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; @@ -300,7 +311,9 @@ CG_CheckLocalSounds ================== */ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { - int highScore, health, armor, reward; + int highScore, reward; + // BFP - Unused in Q3? What? + // int health, armor; sfxHandle_t sfx; // don't play the sounds if the player just changed teams @@ -311,9 +324,14 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { // hit changes if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { cg.opponentHitBlendTime = cg.time + 0.5f; // BFP - For crosshair opponent hit effect - armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; - health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; - trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + // BFP - Unused in Q3? What? + // armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; + // health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; + + // BFP - Play hit sound + if ( cg_playHitSound.integer > 0 ) { + 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 ); } @@ -333,6 +351,14 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { // reward sounds reward = qfalse; + if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { + sfx = cgs.media.excellentSound; + pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); + reward = qtrue; + //Com_Printf("excellent\n"); + } + // BFP - No impressive, gauntlet, defend, assist and capture counters +#if 0 if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); reward = qtrue; @@ -344,12 +370,6 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { reward = qtrue; //Com_Printf("impressive\n"); } - if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { - sfx = cgs.media.excellentSound; - pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); - reward = qtrue; - //Com_Printf("excellent\n"); - } if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { sfx = cgs.media.humiliationSound; pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); @@ -366,8 +386,11 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { reward = qtrue; //Com_Printf("assist\n"); } +#endif // if any of the player event bits changed if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { + // BFP - No denied and gauntlet's humiliation sounds +#if 0 if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); @@ -376,7 +399,9 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); } - else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != + else +#endif + if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) { trap_S_StartLocalSound( cgs.media.holyShitSound, CHAN_ANNOUNCER ); } @@ -386,8 +411,7 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { // check for flag pickup if ( cgs.gametype >= GT_TEAM ) { if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) || - (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) || - (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG]) ) + (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) ) { trap_S_StartLocalSound( cgs.media.youHaveFlagSound, CHAN_ANNOUNCER ); } diff --git a/source/cgame/cg_predict.c b/source/cgame/cg_predict.c index 9e1e1cb..4533079 100644 --- a/source/cgame/cg_predict.c +++ b/source/cgame/cg_predict.c @@ -253,6 +253,140 @@ static void CG_InterpolatePlayerState( qboolean grabAngles ) { } + +int eventStack; +entity_event_t events[ MAX_PREDICTED_EVENTS ]; +int eventParms[ MAX_PREDICTED_EVENTS ]; +int eventParm2[ MAX_PREDICTED_EVENTS ]; // client entity index + +void CG_AddFallDamage( int damage ); + +/* +=================== +CG_StoreEvents + +Save events that may be dropped during prediction +=================== +*/ +void CG_StoreEvent( entity_event_t evt, int eventParm, int entityNum ) +{ + if ( eventStack >= MAX_PREDICTED_EVENTS ) + return; + +// BFP - Remove that in the future >:( +#if 0 + if ( evt == EV_FALL_FAR ) { + CG_AddFallDamage( 10 ); + } else if ( evt == EV_FALL_MEDIUM ) { + CG_AddFallDamage( 5 ); + } +#endif + + events[ eventStack ] = evt; + eventParms[ eventStack ] = eventParm; + eventParm2[ eventStack ] = entityNum; + eventStack++; +} + + +/* +=================== +CG_PlayDroppedEvents +=================== +*/ +void CG_PlayDroppedEvents( playerState_t *ps, playerState_t *ops ) { + centity_t *cent; + entity_event_t oldEvent; + int i, oldParam; + + if ( ps == ops ) { + return; + } + + if ( eventStack <= MAX_PS_EVENTS ) { + return; + } + + cent = &cg.predictedPlayerEntity; + + oldEvent = cent->currentState.event; + oldParam = cent->currentState.eventParm; + + for ( i = 0; i < eventStack - MAX_PS_EVENTS ; i++ ) { + cent->currentState.event = events[ i ]; + cent->currentState.eventParm = eventParms[ i ]; + if ( cg_showmiss.integer ) + { + CG_Printf( "Playing dropped event: %s %i", eventnames[ events[ i ] ], eventParms[ i ] ); + } + CG_EntityEvent( cent, cent->lerpOrigin, eventParm2[ i ] ); + cg.eventSequence++; + } + + cent->currentState.event = oldEvent; + cent->currentState.eventParm = oldParam; +} + +// BFP - When powerlevel is lesser than 10, (notorious between 0 and 4), +// the camera yaw view changes abruptly when the player is spawned at the first time falling far. +// Remove that in the future +// It doesn't look useful at all, it's a bug or an annoying glitch >:( +#if 0 +static int CG_CheckArmor( int damage ) { + int save; + int count; + + count = cg.predictedPlayerState.stats[STAT_ARMOR]; + + save = ceil( damage * ARMOR_PROTECTION ); + + if (save >= count) + save = count; + + if ( !save ) + return 0; + + cg.predictedPlayerState.stats[STAT_ARMOR] -= save; + + return save; +} + + +void CG_AddFallDamage( int damage ) +{ + int take, asave; + + // BFP - No battlesuit powerup +#if 0 + if ( cg.predictedPlayerState.powerups[ PW_BATTLESUIT ] ) + return; +#endif + + if ( cg.predictedPlayerState.clientNum != cg.snap->ps.clientNum || cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + take = damage; + + asave = CG_CheckArmor( take ); + + take -= asave; + + cg.predictedPlayerState.stats[STAT_HEALTH] -= take; + +#if 0 + CG_Printf( "take: %i asave:%i health:%i armor:%i\n", take, asave, + cg.predictedPlayerState.stats[STAT_HEALTH], cg.predictedPlayerState.stats[STAT_ARMOR] ); +#endif + + cg.predictedPlayerState.damagePitch = 255; + cg.predictedPlayerState.damageYaw = 255; + //cg.predictedPlayerState.damageEvent++; + cg.predictedPlayerState.damageCount = take + asave; +} +#endif + + /* =================== CG_TouchItem @@ -291,7 +425,7 @@ static void CG_TouchItem( centity_t *cent ) { } // grab it - BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState, cent - cg_entities ); // remove it from the frame so it won't be drawn cent->currentState.eFlags |= EF_NODRAW; @@ -564,9 +698,30 @@ void CG_PredictPlayerState( void ) { } } - // don't predict gauntlet firing, which is only supposed to happen - // when it actually inflicts damage - cg_pmove.gauntletHit = qfalse; + // BFP - don't predict melee attack, which is only supposed to happen + // when it actually inflicts damage (on Q3 comment says the same, but with gauntlet :P) + cg_pmove.meleeHit = qfalse; + + // BFP - Melee only and no flight pmove handling + { + int gValue; + const char *info; + + info = CG_ConfigString( CS_SERVERINFO ); + // BFP - Melee only + cg_pmove.meleeOnly = qfalse; + gValue = atoi( Info_ValueForKey( info, "g_meleeOnly" ) ); + if ( gValue > 0 ) { + cg_pmove.meleeOnly = qtrue; + } + + // BFP - No flight + cg_pmove.noFlight = qfalse; + gValue = atoi( Info_ValueForKey( info, "g_noFlight" ) ); + if ( gValue > 0 ) { + cg_pmove.noFlight = qtrue; + } + } if ( cg_pmove.pmove_fixed ) { cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; diff --git a/source/cgame/cg_scoreboard.c b/source/cgame/cg_scoreboard.c index 00efc41..8eab883 100644 --- a/source/cgame/cg_scoreboard.c +++ b/source/cgame/cg_scoreboard.c @@ -95,14 +95,7 @@ static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); // draw the handicap or bot skill marker (unless player has flag) - if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if( largeFormat ) { - CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); - } - else { - CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); - } - } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { + if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); } @@ -126,21 +119,29 @@ static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); } } - } else if ( ci->handicap < 100 ) { + } + // BFP - No handicap +#if 0 + else if ( ci->handicap < 100 ) { Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); if ( cgs.gametype == GT_TOURNAMENT ) CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); else CG_DrawSmallStringColor( iconx, y, string, color ); } +#endif // draw the wins / losses if ( cgs.gametype == GT_TOURNAMENT ) { Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); + // BFP - No handicap +#if 0 if( ci->handicap < 100 && !ci->botSkill ) { CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); } - else { + else +#endif + { CG_DrawSmallStringColor( iconx, y, string, color ); } } @@ -161,9 +162,14 @@ static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, if ( score->ping == -1 ) { Com_sprintf(string, sizeof(string), " connecting %s", ci->name); - } else if ( ci->team == TEAM_SPECTATOR ) { + } else if ( ci->team == TEAM_SPECTATOR + && cgs.gametype != GT_SURVIVAL ) { // BFP - Survival, don't draw SPECT word on score column Com_sprintf(string, sizeof(string), " SPECT %3i %4i %s", score->ping, score->time, ci->name); + } else if ( cgs.gametype == GT_MONSTER // BFP - Monster gamemode, mark the player who is monster + && ( cg_entities[score->client].currentState.eFlags & EF_MONSTER ) ) { + Com_sprintf(string, sizeof(string), + "MON%2i %3i %4i %s", score->score, score->ping, score->time, ci->name); } else { Com_sprintf(string, sizeof(string), "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); @@ -379,17 +385,37 @@ qboolean CG_DrawOldScoreboard( void ) { y += (n2 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n2; } + + // BFP - Team Last Man Standing, a second entry drawn... to identify the gametype, maybe? + if ( cgs.gametype == GT_TLMS ) { + y += lineHeight/2; + n1 = CG_TeamScoreboard( y, TEAM_RED, fade, 0, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, 0, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + } n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; } else { - // - // free for all scoreboard - // - n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); - y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + // BFP - Survival gametype, draws the blue line status for these who are in the match + if ( cgs.gametype == GT_SURVIVAL ) { + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + } else { + // + // free for all scoreboard + // + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + } } if (!localClient) { diff --git a/source/cgame/cg_servercmds.c b/source/cgame/cg_servercmds.c index e1f45a7..f86dcc3 100644 --- a/source/cgame/cg_servercmds.c +++ b/source/cgame/cg_servercmds.c @@ -71,6 +71,7 @@ static void CG_ParseScores( void ) { } } + /* ================= CG_ParseTeamInfo @@ -118,6 +119,10 @@ void CG_ParseServerinfo( void ) { cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + + // BFP - Monster gamemode, check if it can load the monster model + cgs.monster = atoi( Info_ValueForKey( info, "g_monster" ) ); + mapname = Info_ValueForKey( info, "mapname" ); Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); @@ -462,7 +467,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in } len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { + if ( f == FS_INVALID_HANDLE ) { trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); return qfalse; } @@ -485,7 +490,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in voiceChats[i].id[0] = 0; } token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (token[0] == '\0') { return qtrue; } if (!Q_stricmp(token, "female")) { @@ -505,7 +510,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in voiceChatList->numVoiceChats = 0; while ( 1 ) { token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (token[0] == '\0') { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); @@ -517,7 +522,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in voiceChats[voiceChatList->numVoiceChats].numSounds = 0; while(1) { token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (token[0] == '\0') { return qtrue; } if (!Q_stricmp(token, "}")) @@ -525,7 +530,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in sound = trap_S_RegisterSound( token, compress ); voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (token[0] == '\0') { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ @@ -575,7 +580,7 @@ int CG_HeadModelVoiceChats( char *filename ) { char *token; len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { + if ( f == FS_INVALID_HANDLE ) { //trap_Print( va( "voice chat file not found: %s\n", filename ) ); return -1; } @@ -593,7 +598,7 @@ int CG_HeadModelVoiceChats( char *filename ) { p = &ptr; token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if ( token[0] == '\0' ) { return -1; } @@ -687,7 +692,7 @@ voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { 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].name[0] != '\0') { if (voiceChatLists[i].gender == gender) { // store this head model with voice chat for future reference for ( j = 0; j < MAX_HEADMODELS; j++ ) { @@ -879,7 +884,7 @@ static void CG_ServerCommand( void ) { } // loaddeferred can be both a servercmd and a consolecmd - if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo + if ( !strcmp( cmd, "loaddefered" ) ) { CG_LoadDeferredPlayers(); return; } diff --git a/source/cgame/cg_snapshot.c b/source/cgame/cg_snapshot.c index f337e08..f066af1 100644 --- a/source/cgame/cg_snapshot.c +++ b/source/cgame/cg_snapshot.c @@ -186,6 +186,14 @@ static void CG_TransitionSnapshot( void ) { } } + // BFP - When the player is flying and looking vertically, + // adjust the mouse's yaw direction to prevent the player from + // looking in the incorrect horizontal direction + if ( cg.refdefViewAngles[PITCH] <= -100 || cg.refdefViewAngles[PITCH] >= 100 ) { + trap_SendConsoleCommand( "seta m_yaw -0.022\n" ); + } else { + trap_SendConsoleCommand( "seta m_yaw 0.022\n" ); + } } diff --git a/source/cgame/cg_syscalls.c b/source/cgame/cg_syscalls.c index 77449a6..8d6779a 100644 --- a/source/cgame/cg_syscalls.c +++ b/source/cgame/cg_syscalls.c @@ -31,7 +31,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; -void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) { +DLLEXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) { syscall = syscallptr; } @@ -98,7 +98,7 @@ void trap_FS_FCloseFile( fileHandle_t f ) { syscall( CG_FS_FCLOSEFILE, f ); } -int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { +int trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ) { return syscall( CG_FS_SEEK, f, offset, origin ); } diff --git a/source/cgame/cg_trails.c b/source/cgame/cg_trails.c new file mode 100644 index 0000000..47f863a --- /dev/null +++ b/source/cgame/cg_trails.c @@ -0,0 +1,560 @@ +/* +=========================================================================== + +BFP TRAILS + +=========================================================================== +*/ + + +#include "cg_local.h" + +#define TRAIL_SEGMENTS 99 +#define CORKSCREW_SEGMENTS 150 +#define MISSILE_TRAIL_SEGMENTS 120 + +typedef struct { + vec3_t segments[MISSILE_TRAIL_SEGMENTS]; + int segmentTime[MISSILE_TRAIL_SEGMENTS]; + vec3_t color; + qboolean rainbow; + int rainbowStartTime; + qhandle_t shader; + int numSegments; +} trail_t; + +static trail_t cg_trails[MAX_GENTITIES][3]; +static vec3_t spiralSegments[CORKSCREW_SEGMENTS]; + +/* +=============== +CG_InitTrails + +Initializes the array of trails for all centities. +Should be called from CG_Init in cg_main.c +=============== +*/ +void CG_InitTrails( void ) { + memset( &cg_trails, 0, sizeof(cg_trails) ); +} + +/* +=============== +CG_ResetTrail + +Reset entity's trail. +Should be called whenever an entity that has to use a trail, wasn't in the PVS the previous frame. +TRAIL_TYPE: Trail type +entityNum: Valid entity number +origin: Point from where the trail should start. + (This should be equal to the entity's current position.) +===================== +*/ +void CG_ResetTrail( const int TRAIL_TYPE, int entityNum, vec3_t origin ) { + int i; + + for ( i = 0; i < TRAIL_SEGMENTS; ++i ) { + VectorCopy( origin, cg_trails[entityNum][TRAIL_TYPE].segments[i] ); + cg_trails[entityNum][TRAIL_TYPE].segmentTime[i] = 0; + } + cg_trails[entityNum][TRAIL_TYPE].numSegments = 0; +} + +/* +===================== +CG_KiTrail + +Adds ki trail segments +===================== +*/ +void CG_KiTrail( int entityNum, vec3_t origin, qboolean remove, qhandle_t hShader ) { + int i; + int kiTrailLength = cg_kiTrail.integer; + trail_t *kiTrail = &cg_trails[entityNum][KI_TRAIL]; + const int NORMAL_KI_TRAIL_WIDTH = 15; + const int MONSTER_KI_TRAIL_WIDTH = 150; + const int OVERLAP_TIMES = 3; + float kiTrailWidth = // for the player monster + ( cg_entities[entityNum].currentState.eFlags & EF_MONSTER ) + ? MONSTER_KI_TRAIL_WIDTH + : NORMAL_KI_TRAIL_WIDTH; +// BFP - A macro to enable/disable poly/refEntity_t rendering. If enabled, render polys, if disabled render the original BFP refEntity_t trails +#define POLYVERT_KI_TRAILS 1 + + if ( entityNum < 0 || entityNum >= MAX_GENTITIES + || kiTrailLength <= OVERLAP_TIMES ) { // don't draw if ki trail length is less than the number of overlap times + return; + } + + if ( kiTrailLength > TRAIL_SEGMENTS ) { + kiTrailLength = TRAIL_SEGMENTS; + } + + if ( remove ) { // removes every segment + kiTrail->numSegments = ( kiTrail->numSegments > 0 ) + ? kiTrail->numSegments - 1 + : 0; + } else { + if ( kiTrail->numSegments < kiTrailLength ) { + ++kiTrail->numSegments; + } + } + + // shift points down the buffer + for ( i = kiTrail->numSegments - 1; i > 0; --i ) { + VectorCopy( kiTrail->segments[i - 1], kiTrail->segments[i] ); + } + + // add the current position at the start + VectorCopy( origin, kiTrail->segments[0] ); + + for ( i = 0; i < kiTrail->numSegments - OVERLAP_TIMES; ++i ) { + vec3_t start, end; + + VectorCopy( kiTrail->segments[i], start ); + VectorCopy( kiTrail->segments[i + OVERLAP_TIMES], end ); + +#if POLYVERT_KI_TRAILS + // render the polys + { + polyVert_t verts[4]; + vec3_t forward, right; + vec3_t viewAxis; + + VectorSubtract( end, start, forward ); + VectorNormalize( forward ); + + VectorSubtract( cg.refdef.vieworg, start, viewAxis ); + CrossProduct( viewAxis, forward, right ); + VectorNormalize( right ); + + VectorMA( start, kiTrailWidth, right, verts[0].xyz ); + Vector2Set( verts[0].st, 0, 0 ); + Byte4Set( verts[0].modulate, 255, 255, 255, 255 ); + + VectorMA( end, kiTrailWidth, right, verts[1].xyz ); + Vector2Set( verts[1].st, 1, 0 ); + Byte4Set( verts[1].modulate, 255, 255, 255, 255 ); + + VectorMA( end, -kiTrailWidth, right, verts[2].xyz ); + Vector2Set( verts[2].st, 1, 1 ); + Byte4Set( verts[2].modulate, 255, 255, 255, 255 ); + + VectorMA( start, -kiTrailWidth, right, verts[3].xyz ); + Vector2Set( verts[3].st, 0, 1 ); + Byte4Set( verts[3].modulate, 255, 255, 255, 255 ); + + trap_R_AddPolyToScene( hShader, 4, verts ); + } +#else + { // I see... so, BFP originally used RT_RAIL_CORE, they didn't care the size, it was already set + refEntity_t trail; + memset( &trail, 0, sizeof( trail ) ); + trail.reType = RT_RAIL_CORE; + trail.customShader = hShader; + VectorCopy( start, trail.origin ); + VectorCopy( end, trail.oldorigin ); + trail.shaderRGBA[0] = trail.shaderRGBA[1] = trail.shaderRGBA[2] = trail.shaderRGBA[3] = 255; + + trap_R_AddRefEntityToScene( &trail ); + } +#endif + } +} + +/* +===================== +CG_BeamTrail + +Adds beam trail segments +===================== +*/ +void CG_BeamTrail( int entityNum, vec3_t origin, vec3_t muzzleOrigin, qhandle_t hShader ) { + int i; + int nBeamSegments = cg_beamTrail.integer; + trail_t *beamTrail = &cg_trails[entityNum][BEAM_TRAIL]; + const float BEAM_STRAIGHTEN_RATE = 0.2f; // how quickly segments straighten (0.0 - 1.0) + + if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) { + return; + } + + // for better visual bendy effect, the number of segments should be equal or more than 10 + if ( nBeamSegments < 10 ) { + nBeamSegments = 2; + } + + if ( nBeamSegments > TRAIL_SEGMENTS ) { + nBeamSegments = TRAIL_SEGMENTS; + } + + beamTrail->numSegments = nBeamSegments; + + VectorCopy( muzzleOrigin, beamTrail->segments[0] ); + VectorCopy( origin, beamTrail->segments[nBeamSegments - 1] ); + + // start stretching segments + if ( nBeamSegments >= 10 ) { + vec3_t currentAngles, beamDir; + float beamLength, lengthFactor; + + // beam length and direction + VectorSubtract( origin, muzzleOrigin, beamDir ); + beamLength = VectorLength( beamDir ); + VectorNormalize( beamDir ); + vectoangles( beamDir, currentAngles ); + + // length factor for straightening based on beam length + // longer beam = more straightening (less curve) + // reference length of 5000 units as baseline + lengthFactor = 1.0f + ( beamLength / 5000.0f ); + if ( lengthFactor > 2.5f ) { + lengthFactor = 2.5f; + } + + // shift all segments down by one position (creating trail effect) + for ( i = nBeamSegments - 1; i > 0; --i ) { + VectorCopy( beamTrail->segments[i - 1], beamTrail->segments[i] ); + } + + // update all segments with distance-based straightening + for ( i = 1; i < nBeamSegments; ++i ) { + vec3_t targetPos, segmentToOrigin; + float distanceToOrigin, segmentFraction; + float straightenFactor, maxExpectedDistance, stretchRatio = 1.0f; + + // ideal position on straight line from muzzle to origin + segmentFraction = (float)i / (float)( nBeamSegments - 1 ); + VectorMA( muzzleOrigin, segmentFraction * beamLength, beamDir, targetPos ); + + // how far this segment is from the impact point + VectorSubtract( origin, beamTrail->segments[i], segmentToOrigin ); + distanceToOrigin = VectorLength( segmentToOrigin ); + + // expected distance if beam was straight + maxExpectedDistance = beamLength * ( 1.0f - segmentFraction ); + + // stretch ratio (how much segments are stretched) + if ( maxExpectedDistance > 0.1f ) { + stretchRatio = distanceToOrigin / maxExpectedDistance; + } + + if ( stretchRatio > 1.0f ) { + // segments are stretching, straighten them out more aggressively + // length factor: longer beams straighten more + straightenFactor = BEAM_STRAIGHTEN_RATE * stretchRatio * lengthFactor; + if ( straightenFactor > 1.0f ) { + straightenFactor = 1.0f; + } + } else { + // not aiming and not stretched - gentle straightening + // length factor: longer beams straighten faster + straightenFactor = BEAM_STRAIGHTEN_RATE * lengthFactor; + if ( straightenFactor > 1.0f ) { + straightenFactor = 1.0f; + } + } + + // transition toward target position + VectorScale( beamTrail->segments[i], 1.0f - straightenFactor, beamTrail->segments[i] ); + VectorMA( beamTrail->segments[i], straightenFactor, targetPos, beamTrail->segments[i] ); + } + } + + // draw every beam segment + { + refEntity_t beam; + memset( &beam, 0, sizeof( beam ) ); + + beam.customShader = hShader; + beam.shaderRGBA[0] = beam.shaderRGBA[1] = beam.shaderRGBA[2] = beam.shaderRGBA[3] = 0xff; + beam.reType = RT_LIGHTNING; + + if ( nBeamSegments == 2 ) { + VectorCopy( beamTrail->segments[0], beam.origin ); + VectorCopy( beamTrail->segments[1], beam.oldorigin ); + trap_R_AddRefEntityToScene( &beam ); + return; + } + + // BFP - NOTE: Skip the rendering of the 2 last segments. + // Weird. That's why the bendy beam can't visualize that segment correctly. + // On original BFP also happens + for ( i = 0; i < nBeamSegments - 2; ++i ) { + VectorCopy( beamTrail->segments[i], beam.origin ); + if ( i > 0 ) { + VectorCopy( beamTrail->segments[i - 1], beam.origin ); + } + VectorCopy( beamTrail->segments[i + 1], beam.oldorigin ); + trap_R_AddRefEntityToScene( &beam ); + beam.reType = RT_SPRITE; + trap_R_AddRefEntityToScene( &beam ); + beam.reType = RT_LIGHTNING; + } + } +} + + +/* +===================== +CG_CorkscrewTrail + +Adds corkscrew trail segments +===================== +*/ +void CG_CorkscrewTrail( int entityNum, vec3_t origin, vec3_t muzzleOrigin, qhandle_t beamShader, qhandle_t corkscrewShader ) { + int i; + float length; + vec3_t fullDir, forward, right, up; + vec3_t temp = {0, 0, 1}; + const float ROTATIONS = 5.0f; + const float MAX_RADIUS = 12.0f; + const int TAPER_SEGMENTS = 7; + + if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) { + return; + } + + VectorCopy( origin, spiralSegments[CORKSCREW_SEGMENTS - 1] ); + VectorCopy( muzzleOrigin, spiralSegments[0] ); + + // starting corkscrew segment spiral positions + VectorSubtract( origin, muzzleOrigin, fullDir ); + length = VectorNormalize( fullDir ); + VectorCopy( fullDir, forward ); + + // generate robust perpendicular vectors + CrossProduct( forward, temp, right ); + VectorNormalize( right ); + CrossProduct( forward, right, up ); + VectorNormalize( up ); + + // generate spiral points around the beam + for ( i = 0; i < CORKSCREW_SEGMENTS; ++i ) { + float t = (float)i / (float)( CORKSCREW_SEGMENTS - 1 ); + vec3_t basePoint, offset, radial; + float radius = MAX_RADIUS; + float angle = ROTATIONS * 2 * M_PI * t; + + // point along the beam + VectorMA( muzzleOrigin, t * length, forward, basePoint ); + + // calculate spiral offset (orbit around beam) + VectorScale( right, cos( angle ), radial ); + VectorMA( radial, sin( angle ), up, radial ); + VectorNormalize( radial ); + + // taper radius near muzzle for the first few segments + if ( i < TAPER_SEGMENTS ) { + // linear taper: radius starts small and increases + radius = MAX_RADIUS * ( i / (float)TAPER_SEGMENTS ); + } + // taper radius near origin for the last few segments + else if ( i > CORKSCREW_SEGMENTS - ( TAPER_SEGMENTS + 14 ) - 1 ) { + // linear taper: radius decreases to zero at the end + int segmentsFromEnd = CORKSCREW_SEGMENTS - 1 - i; + radius = MAX_RADIUS * ( segmentsFromEnd / (float)( TAPER_SEGMENTS + 14 ) ); + } + VectorScale( radial, radius, offset ); + + // final spiral point + VectorAdd( basePoint, offset, spiralSegments[i] ); + } + + { + // draw beam segment + refEntity_t beam; + memset( &beam, 0, sizeof( beam ) ); + beam.reType = RT_RAIL_CORE; + beam.customShader = beamShader; + beam.shaderRGBA[0] = beam.shaderRGBA[1] = beam.shaderRGBA[2] = beam.shaderRGBA[3] = 0xff; + VectorCopy( muzzleOrigin, beam.origin ); + VectorCopy( origin, beam.oldorigin ); + + trap_R_AddRefEntityToScene( &beam ); + + // draw every corkscrew segment + beam.customShader = corkscrewShader; + + for ( i = 0; i < CORKSCREW_SEGMENTS - 1; ++i ) { + VectorCopy( spiralSegments[i], beam.origin ); + if ( i > 0 ) { + VectorCopy( spiralSegments[i - 1], beam.origin ); + } + VectorCopy( spiralSegments[i + 1], beam.oldorigin ); + trap_R_AddRefEntityToScene( &beam ); + } + } +} + + +/* +===================== +HSVtoRGB +===================== +*/ +static float ModFloat( float a, float b ) { + return ( b <= 0.0f ) ? 0 : ( a - (int)( a / b ) * b ); +} +static void HSVtoRGB( float h, float s, float v, float *r, float *g, float *b ) { + int i; + float f, p, q, t; + h = ModFloat( h, 1.0f ); + if ( s <= 0.0f ) { + *r = *g = *b = v; + return; + } + h *= 6.0f; + i = floor( h ); + f = h - i; + p = v * ( 1.0f - s ); + q = v * ( 1.0f - s * f ); + t = v * ( 1.0f - s * ( 1.0f - f ) ); + switch ( i ) { + case 0: *r = v; *g = t; *b = p; break; + case 1: *r = q; *g = v; *b = p; break; + case 2: *r = p; *g = v; *b = t; break; + case 3: *r = p; *g = q; *b = v; break; + case 4: *r = t; *g = p; *b = v; break; + default:*r = v; *g = p; *b = q; break; + } +} + + +/* +===================== +CG_MissileTrail + +Just adds segments, doesn't draw +===================== +*/ +void CG_MissileTrail( int entityNum, vec3_t origin, qhandle_t hShader, vec3_t color, qboolean rainbow ) { + int i; + trail_t *missileTrail = &cg_trails[entityNum][MISSILE_TRAIL]; + + if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) { + return; + } + + if ( missileTrail->numSegments == 0 ) { + missileTrail->rainbowStartTime = cg.time; + } + + if ( missileTrail->numSegments < MISSILE_TRAIL_SEGMENTS ) { + ++missileTrail->numSegments; + } + + for ( i = missileTrail->numSegments - 1; i > 0; --i ) { + VectorCopy( missileTrail->segments[i-1], missileTrail->segments[i] ); + missileTrail->segmentTime[i] = missileTrail->segmentTime[i-1]; + } + + VectorCopy( origin, missileTrail->segments[0] ); + missileTrail->segmentTime[0] = cg.time; + missileTrail->shader = hShader; + VectorCopy( color, missileTrail->color ); + missileTrail->rainbow = rainbow; +} + + +/* +===================== +CG_DrawMissileTrails + +Called once per frame. +Draw all active missile segments of all entities +===================== +*/ +void CG_DrawMissileTrails( void ) { + int entityNum, i; + const int SEGMENT_LIFESPAN_MSEC = 900; + const float START_WIDTH = 15.0f; + const float END_WIDTH = 15.0f; + + for ( entityNum = 0; entityNum < MAX_GENTITIES; ++entityNum ) { + trail_t *trail = &cg_trails[entityNum][MISSILE_TRAIL]; + + for ( i = 0; i < trail->numSegments - 1; ++i ) { + vec3_t start, end; + float alpha, width; + byte alphaByte; + int age = cg.time - trail->segmentTime[i]; + int ageNext = cg.time - trail->segmentTime[i + 1]; + + if ( age >= SEGMENT_LIFESPAN_MSEC ) { + continue; + } + + // the old end of the quad has expired — don't draw, + // but there might be new segments later in the buffer, + // so we use continue instead of break + if ( ageNext >= SEGMENT_LIFESPAN_MSEC ) { + continue; + } + + // temporal discontinuity: the two ends belong to different firings. + // a gap greater than ~150ms between consecutive segments means that the + // buffer was refilled by a new missile on top of the remains of the previous one + if ( trail->segmentTime[i] - trail->segmentTime[i + 1] > 150 ) { + continue; + } + + VectorCopy( trail->segments[i], start ); + VectorCopy( trail->segments[i + 1], end ); + + // calculate opacity and width according to age + alpha = 1.0f - (float)age / SEGMENT_LIFESPAN_MSEC; + if ( alpha < 0.0f ) alpha = 0.0f; + alphaByte = (byte)(alpha * 255.0f); + width = START_WIDTH * ( 1.0f - (float)age / SEGMENT_LIFESPAN_MSEC ) + + END_WIDTH * ( (float)age / SEGMENT_LIFESPAN_MSEC ); + + // render the polys + { + vec3_t forward, right, viewAxis; + polyVert_t verts[4]; + byte r, g, b; + + r = (byte)(trail->color[0] * alphaByte); + g = (byte)(trail->color[1] * alphaByte); + b = (byte)(trail->color[2] * alphaByte); + + // rainbow effect + if ( trail->rainbow ) { + float rf, gf, bf; + float elapsed = ( cg.time - trail->rainbowStartTime ) * 0.0006f; + float hue = (float)age / SEGMENT_LIFESPAN_MSEC; + hue = ModFloat( elapsed, 1.0f ); + HSVtoRGB( hue, 1.0f, 1.0f, &rf, &gf, &bf ); + r = (byte)(rf * alphaByte); + g = (byte)(gf * alphaByte); + b = (byte)(bf * alphaByte); + } + + VectorSubtract( end, start, forward ); + VectorNormalize( forward ); + VectorSubtract( cg.refdef.vieworg, start, viewAxis ); + CrossProduct( viewAxis, forward, right ); + VectorNormalize( right ); + + VectorMA( start, width, right, verts[0].xyz ); + Vector2Set( verts[0].st, 0, 0 ); + Byte4Set( verts[0].modulate, r, g, b, alphaByte ); + + VectorMA( end, width, right, verts[1].xyz ); + Vector2Set( verts[1].st, 1, 0 ); + Byte4Set( verts[1].modulate, r, g, b, alphaByte ); + + VectorMA( end, -width, right, verts[2].xyz ); + Vector2Set( verts[2].st, 1, 1 ); + Byte4Set( verts[2].modulate, r, g, b, alphaByte ); + + VectorMA( start, -width, right, verts[3].xyz ); + Vector2Set( verts[3].st, 0, 1 ); + Byte4Set( verts[3].modulate, r, g, b, alphaByte ); + + trap_R_AddPolyToScene( trail->shader, 4, verts ); + } + } + } +} diff --git a/source/cgame/cg_view.c b/source/cgame/cg_view.c index 67cb3f5..e0e5b2c 100644 --- a/source/cgame/cg_view.c +++ b/source/cgame/cg_view.c @@ -225,18 +225,10 @@ static void CG_OffsetThirdPersonView( void ) { trace_t trace; static vec3_t mins = { -4, -4, -4 }; static vec3_t maxs = { 4, 4, 4 }; - // vec3_t focusPoint; // BFP - unused - // float focusDist; // BFP - unused float forwardScale, sideScale; // BFP - Camera setup variables vec3_t overrideOrg; float camAngle, camHeight, camRange; - // BFP - Fly tilt - int cmdNum; - usercmd_t cmd; - // BFP - Last angled for fly tilt angle to move smoothly similar to BFP vanilla - static float lastAngled = 0.0f, lastRightAngled = 0.0f, lastUpAngled = 0.0f; - float rightAngled, upAngled; // BFP - Camera setup camAngle = cg_thirdPersonAngle.value; @@ -247,35 +239,36 @@ static void CG_OffsetThirdPersonView( void ) { camHeight = -60.0f; camRange = 110.0f; } - VectorCopy( cg.refdef.vieworg, overrideOrg ); - // cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; // BFP - BFP camera position doesn't use that to move to player's height + // BFP - Monster gamemode, force player monster third person camera views + if ( cg.predictedPlayerState.eFlags & EF_MONSTER ) { +// BFP - Macro to enable/disable original player monster camera view position +#define BFP_MONSTER_CAMERA_VIEWPOS 1 +#if BFP_MONSTER_CAMERA_VIEWPOS + // BFP original camera view position + camHeight = -385.0f; + camRange = 500.0f; +#else + // far camera view position similar to BFP fixed third person camera + camHeight = -530.0f; + camRange = 900.0f; +#endif + } + VectorCopy( cg.refdef.vieworg, overrideOrg ); 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]; + int totalYaw = cg.predictedPlayerState.damageYaw + cg.predictedPlayerState.damagePitch; + focusAngles[YAW] = totalYaw; + cg.refdefViewAngles[YAW] = totalYaw; } -// BFP - unused check -#if 0 - if ( focusAngles[PITCH] > 45 ) { - focusAngles[PITCH] = 45; // don't go too far overhead - } -#endif - AngleVectors( focusAngles, forward, NULL, NULL ); - // VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); // BFP - unused - VectorCopy( cg.refdef.vieworg, view ); - // view[2] += 8; // // BFP - unused Q3 default view height value - - // cg.refdefViewAngles[PITCH] *= 0.5; // BFP - unused - AngleVectors( cg.refdefViewAngles, forward, right, up ); // BFP - Camera height setup @@ -284,22 +277,15 @@ static void CG_OffsetThirdPersonView( void ) { forwardScale = cos( camAngle / 180 * M_PI ); sideScale = sin( camAngle / 180 * M_PI ); -// BFP - unused -#if 0 - VectorMA( view, -camRange * forwardScale, forward, view ); - VectorMA( view, -camRange * sideScale, right, view ); -#endif - - // 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 - // BFP - cg_cameraMode cvar to detect if it's disabled doesn't exist + // BFP - NOTE: Originally, BFP uses MASK_SOLID for tracing and it might not be a good solution, so use MASK_PLAYERSOLID for all traces here instead in the future // That traces the camera pivot + // 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 ) { - // BFP - Use the vector scale to trace something solid and add endpos + // BFP - Use the vector scale to trace something solid VectorScale( trace.plane.normal, camRange, view ); - VectorAdd( trace.endpos, view, cg.refdef.vieworg ); view[2] += (1.0 - trace.fraction) * 32; // try another trace to this position, because a tunnel may have the ceiling @@ -309,58 +295,15 @@ static void CG_OffsetThirdPersonView( void ) { VectorCopy( trace.endpos, view ); } -// BFP - unused -#if 0 - 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; -#endif - // BFP - Camera setup focusAngles[YAW] -= camAngle; - // BFP - Fly tilt - // Get the pressed keys to move left or right - cmdNum = trap_GetCurrentCmdNumber(); - trap_GetUserCmd( cmdNum, &cmd ); - - focusAngles[ROLL] = LERP( lastAngled, 0.0f, (float)(cg.frametime / 1000.00f) * 20.0f ); - rightAngled = LERP( lastRightAngled, 0.0f, (float)(cg.frametime / 1000.00f) * 20.0f ); - upAngled = LERP( lastUpAngled, 0.0f, (float)(cg.frametime / 1000.00f) * 20.0f ); - - if ( cg_flytilt.integer >= 1 - && ( cg.predictedPlayerState.pm_flags & PMF_FLYING ) - && ( cg.predictedPlayerState.eFlags & EF_AURA ) && &cmd ) { - if ( cmd.rightmove < 0 ) { // Left - focusAngles[ROLL] = LERP( lastAngled, -20.0f, (float)(cg.frametime / 1000.00f) * 15.0f ); - rightAngled = LERP( lastRightAngled, focusAngles[ROLL] - 0.55f, (float)(cg.frametime / 1000.00f) * 15.0f ); - upAngled = LERP( lastUpAngled, -acos( focusAngles[ROLL] / 180 * M_PI ) - 1.7f, (float)(cg.frametime / 1000.00f) * 15.0f ); - } else if ( cmd.rightmove > 0 ) { // Right - focusAngles[ROLL] = LERP( lastAngled, 20.0f, (float)(cg.frametime / 1000.00f) * 15.0f ); - rightAngled = LERP( lastRightAngled, focusAngles[ROLL] - 0.55f, (float)(cg.frametime / 1000.00f) * 15.0f ); - upAngled = LERP( lastUpAngled, -acos( focusAngles[ROLL] / 180 * M_PI ) - 1.7f, (float)(cg.frametime / 1000.00f) * 15.0f ); - } - } - // Last roll where it was "lerped" - lastAngled = focusAngles[ROLL]; - lastRightAngled = rightAngled; - lastUpAngled = upAngled; - VectorCopy( focusAngles, cg.refdefViewAngles ); // VectorCopy( focusAngles, cg.predictedPlayerState.viewangles ); // For player model, doesn't make sense though :P - // BFP - NOTE: Applying angles to height and slide (up and right vectors), while rolling with fly tilt, is an odd case, BFP has something that handles up and right vectors (· ·') *curiosity sweat* - VectorMA( overrideOrg, -camRange * sideScale + rightAngled, right, cg.refdef.vieworg ); + VectorMA( overrideOrg, -camRange * sideScale, right, cg.refdef.vieworg ); VectorMA( cg.refdef.vieworg, -camRange * forwardScale, forward, cg.refdef.vieworg ); - VectorMA( cg.refdef.vieworg, upAngled, up, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, 0, up, cg.refdef.vieworg ); // BFP - Trace the camera position when being near to something solid CG_Trace( &trace, view, mins, maxs, cg.refdef.vieworg, cg.predictedPlayerState.clientNum, MASK_SOLID ); @@ -385,11 +328,8 @@ Returns false if out of view. qboolean CG_WorldCoordToScreenCoordFloat( vec3_t worldCoord, float *x, float *y ) { // BFP - Crosshair functionality float xcenter, ycenter; vec3_t local, transformed; - vec3_t vforward; - vec3_t vright; - vec3_t vup; - float xzi; - float yzi; + vec3_t vforward, vright, vup; + float xzi, yzi; xcenter = 640.0f / 2.0f; // gives screen coords in virtual 640x480, to be adjusted when drawn ycenter = 480.0f / 2.0f; // gives screen coords in virtual 640x480, to be adjusted when drawn @@ -445,6 +385,7 @@ void CG_OffsetFirstPersonView( centity_t *cent, refEntity_t *parent, qhandle_t p vec3_t predictedVelocity; int timeDelta; orientation_t tagOrient; // BFP - First person vis mode orientation setup +#define FPVISMODE_Q3BOBBING_MOVE 0 // BFP - A macro to enable/disable bobbing move like BFP originally does. Disabled by default because looks a little rough if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { return; @@ -457,11 +398,49 @@ void CG_OffsetFirstPersonView( centity_t *cent, refEntity_t *parent, qhandle_t p if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { angles[ROLL] = 40; angles[PITCH] = -15; - angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + angles[YAW] = cg.snap->ps.damageYaw + cg.snap->ps.damagePitch; origin[2] += cg.predictedPlayerState.viewheight; return; } + // BFP - First person vis mode + if ( cg_drawOwnModel.integer >= 1 && parentModel ) { + if ( trap_R_LerpTag( &tagOrient, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, "tag_eyes" ) ) { + VectorCopy( parent->origin, cg.refdef.vieworg ); +#if FPVISMODE_Q3BOBBING_MOVE + // don't bob/duck while on air, flying or charging + if ( ( cg.predictedPlayerState.eFlags & EF_FLIGHT ) + || ( cg.predictedPlayerState.pm_flags & PMF_KI_CHARGE ) + || cg.predictedPlayerState.groundEntityNum == ENTITYNUM_NONE ) { + return; + } + goto _q3fpscam; +#else + return; +#endif + } + + // if tag_eyes doesn't exist, set to tag_head + if ( trap_R_LerpTag( &tagOrient, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, "tag_head" ) ) { + VectorCopy( parent->origin, cg.refdef.vieworg ); +#if FPVISMODE_Q3BOBBING_MOVE + // don't bob/duck while on air, flying or charging + if ( ( cg.predictedPlayerState.eFlags & EF_FLIGHT ) + || ( cg.predictedPlayerState.pm_flags & PMF_KI_CHARGE ) + || cg.predictedPlayerState.groundEntityNum == ENTITYNUM_NONE ) { + return; + } + goto _q3fpscam; +#else + return; +#endif + } + } + +#if FPVISMODE_Q3BOBBING_MOVE +_q3fpscam: +#endif + // add angles based on weapon kick VectorAdd (angles, cg.kick_angles, angles); @@ -518,7 +497,9 @@ void CG_OffsetFirstPersonView( centity_t *cent, refEntity_t *parent, qhandle_t p //=================================== // add view height - origin[2] += cg.predictedPlayerState.viewheight; + if ( cg_drawOwnModel.integer < 1 ) { // BFP - For Q3 first person view + origin[2] += cg.predictedPlayerState.viewheight; + } // smooth out duck height changes timeDelta = cg.time - cg.duckTime; @@ -534,25 +515,6 @@ void CG_OffsetFirstPersonView( centity_t *cent, refEntity_t *parent, qhandle_t p } origin[2] += bob; - - // BFP - First person vis mode - // pivot the eye based on a neck length -#if 1 - if ( cg_drawOwnModel.integer >= 1 ) { -#define NECK_LENGTH 8 - vec3_t forward, up; - - VectorClear( cg.refdefViewAngles ); - - if ( CG_GetTagOrientationFromPlayerEntityParentModel( cent, parent, parentModel, "tag_head", &tagOrient ) ) { - VectorCopy( tagOrient.origin, cg.refdef.vieworg ); - cg.refdef.vieworg[2] -= NECK_LENGTH; - AngleVectors( cg.refdefViewAngles, forward, NULL, up ); - VectorMA( cg.refdef.vieworg, -1, forward, cg.refdef.vieworg ); - VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); - } - } -#endif // add fall height delta = cg.time - cg.landTime; @@ -780,12 +742,24 @@ static int CG_CalcViewValues( void ) { VectorCopy( ps->origin, cg.refdef.vieworg ); + + // BFP - Fly tilt + if ( cg_flytilt.integer <= 0 || ps->pm_type == PM_SPECTATOR ) { + ps->viewangles[ROLL] = 0; // that removes tilt + } VectorCopy( ps->viewangles, cg.refdefViewAngles ); if (cg_cameraOrbit.integer) { if (cg.time > cg.nextOrbitTime) { cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; cg_thirdPersonAngle.value += cg_cameraOrbit.value; + // BFP - If third person angle cvar surpasses 360º degrees, disable forcely camera orbit and set cg_thirdPersonAngle to 0 + if ( cg_thirdPersonAngle.value >= 360.0f ) { + trap_Cvar_Set( "cg_cameraOrbit", "0" ); + trap_Cvar_Set( "cg_thirdPersonAngle", "0" ); + cg_cameraOrbit.value = 0.0f; + cg_thirdPersonAngle.value = 0.0f; + } } } // add error decay @@ -806,14 +780,13 @@ static int CG_CalcViewValues( void ) { if ( cg.renderingThirdPerson && ps->pm_type != PM_SPECTATOR ) { // back away from character CG_OffsetThirdPersonView(); - } -// BFP - Original Q3 first person view function call, not used here, moved to cg_players.c in CG_Player function -#if 0 - else { + } else { // offset for local bobbing and kicks - CG_OffsetFirstPersonView(); + // BFP - That only handles if cg_drawOwnModel is disabled + if ( cg_thirdPerson.integer <= 0 && cg_drawOwnModel.integer <= 0 ) { + CG_OffsetFirstPersonView( NULL, NULL, 0 ); + } } -#endif // position eye reletive to origin AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); @@ -934,7 +907,8 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo CG_PredictPlayerState(); // decide on third person view - cg.renderingThirdPerson = cg_drawOwnModel.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); //cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); + // BFP - Also cg_drawOwnModel handles + cg.renderingThirdPerson = cg_thirdPerson.integer || cg_drawOwnModel.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); // build cg.refdef inwater = CG_CalcViewValues(); @@ -948,8 +922,12 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo if ( !cg.hyperspace ) { CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct CG_AddMarks(); - CG_AddParticles (); + // BFP - Enable/disable particles + if ( cg_particles.integer > 0 ) { + CG_AddParticles (); + } CG_AddLocalEntities(); + CG_DrawMissileTrails(); // BFP - Trails } CG_AddViewWeapon( &cg.predictedPlayerState ); diff --git a/source/cgame/cg_weapons.c b/source/cgame/cg_weapons.c index 60986b9..506ad08 100644 --- a/source/cgame/cg_weapons.c +++ b/source/cgame/cg_weapons.c @@ -24,262 +24,188 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "cg_local.h" /* -========================== -CG_MachineGunEjectBrass -========================== -*/ -static void CG_MachineGunEjectBrass( centity_t *cent ) { - localEntity_t *le; - refEntity_t *re; - vec3_t velocity, xvelocity; - vec3_t offset, xoffset; - float waterScale = 1.0f; - vec3_t v[3]; +=================== +CG_AddFlashMissile - if ( cg_brassTime.integer <= 0 ) { +Adds muzzle flash or missile shader/model +=================== +*/ +void CG_AddFlashMissile( qboolean isMissile, centity_t *cent, int entityNum, vec3_t origin, refEntity_t *parent, char *tagName, char *flashMissileShader, char *flashMissileModel, float flashMissileRadius, float flashMissileScaleFactor ) { // BFP - Flash or missile from player weapon tag + refEntity_t flashMissile; + float missileRadiusChargeMult = 75, missileScaleFactorChargeMult = 1; + float scale = 0; + float minCharge = 2; + float totalCharge = cent->currentState.generic1 - minCharge; + + // don't show the muzzle flash to the player itself on first person camera, even on first person vis mode + if ( cg_thirdPerson.integer <= 0 && entityNum == cg.snap->ps.clientNum ) { return; } - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 0; - velocity[1] = -50 + 40 * crandom(); - velocity[2] = 100 + 50 * crandom(); - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); + if ( totalCharge < 0 ) { + totalCharge = 0; + } - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time - (rand()&15); + if ( flashMissileScaleFactor <= 0 ) { + flashMissileScaleFactor = 1; + } - AnglesToAxis( cent->lerpAngles, v ); + memset( &flashMissile, 0, sizeof( flashMissile ) ); - offset[0] = 8; - offset[1] = -4; - offset[2] = 24; + // BFP - TODO: These function parameters can be applied for the following cfg variable calls: + // - flashModel [attack index] ["name of model"]: The model used for the weapon flash. + // If this is not set or set to 0 and flashShader is set, the flash shader will be treated as a sprite. + + // - flashShader [attack index] ["name of shader"]: The shader used for the flash. + // - flashRadius [attack index] [int]: Only used for sprite flashes. `flashRadius` is the radius of the flash sprite. - 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 ); + // - flashScaleFactor [attack index] [float]: The scale factor used for the flash. + // Used to resize non-sprite flashes. + + // - firingFlashRadius [attack index] [int]: The radius of the firing flash. + // This is used for beam attacks while the beam is firing. + + // - firingFlashScaleFactor [attack index] [float]: The scale factor used for the firing flash. + // Used to resize non-sprite flashes. - VectorCopy( re->origin, le->pos.trBase ); + VectorCopy( origin, flashMissile.origin ); - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } + if ( flashMissileModel && flashMissileModel[0] ) { + flashMissile.reType = RT_MODEL; + flashMissile.hModel = trap_R_RegisterModel( flashMissileModel ); - 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 ); + if ( parent ) { + AxisCopy( parent->axis, flashMissile.axis ); + } else { + AxisClear( flashMissile.axis ); + } - AxisCopy( axisDefault, re->axis ); - re->hModel = cgs.media.machinegunBrassModel; + if ( isMissile ) { + qboolean missileSpinHoriz = qfalse; + scale = flashMissileScaleFactor + missileScaleFactorChargeMult * totalCharge; + + // spin as it moves + if ( !missileSpinHoriz ) { // BFP - For disk or missileSpinHoriz (qboolean) weapons, don't rotate like the rocket + // BFP - missileModelRotation + float missileModelRotation = 0.2; + RotateAroundDirection( flashMissile.axis, cg.time * missileModelRotation ); + } else { + // BFP - Rotate Z-axis like a wheel + vec3_t temp; + RotateAroundDirection( flashMissile.axis, cent->currentState.time ); + + VectorCopy( flashMissile.axis[0], temp ); + RotatePointAroundVector( flashMissile.axis[0], flashMissile.axis[2], temp, cg.autoAnglesFast[1] ); + VectorCopy( flashMissile.axis[1], temp ); + RotatePointAroundVector( flashMissile.axis[1], flashMissile.axis[2], temp, cg.autoAnglesFast[1] ); + } + } else { + scale = flashMissileScaleFactor; + } + CG_ModelSize( &flashMissile, scale ); + } else { + flashMissile.reType = RT_SPRITE; - le->bounceFactor = 0.4 * waterScale; + if ( isMissile ) { + float missileRotation = 15; + scale = flashMissileRadius + missileRadiusChargeMult * totalCharge; + flashMissile.rotation = missileRotation; + } else { + scale = flashMissileRadius; + } + flashMissile.radius = scale; + } - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 2; - le->angles.trDelta[1] = 1; - le->angles.trDelta[2] = 0; + if ( flashMissileShader && flashMissileShader[0] ) { + flashMissile.customShader = trap_R_RegisterShader( flashMissileShader ); + } - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; + flashMissile.shaderRGBA[0] = 0xff; + flashMissile.shaderRGBA[1] = 0xff; + flashMissile.shaderRGBA[2] = 0xff; + flashMissile.shaderRGBA[3] = 0xff; + trap_R_AddRefEntityToScene( &flashMissile ); } + /* ========================== -CG_ShotgunEjectBrass +CG_RailTrail ========================== */ -static void CG_ShotgunEjectBrass( centity_t *cent ) { - localEntity_t *le; - refEntity_t *re; - vec3_t velocity, xvelocity; - vec3_t offset, xoffset; - vec3_t v[3]; - int i; - - if ( cg_brassTime.integer <= 0 ) { - return; - } - - for ( i = 0; i < 2; i++ ) { - float waterScale = 1.0f; - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 60 + 60 * crandom(); - if ( i == 0 ) { - velocity[1] = 40 + 10 * crandom(); - } else { - velocity[1] = -40 + 10 * crandom(); - } - velocity[2] = 100 + 50 * crandom(); - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); - - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time; +void CG_RailTrail ( clientInfo_t *ci, vec3_t start, vec3_t end ) { // BFP - BFP uses an old version of rail trail on Quake 3 Arena 1st version release + localEntity_t *le; + refEntity_t *re; - AnglesToAxis( cent->lerpAngles, v ); + // + // rings + // + le = CG_AllocLocalEntity(); + re = &le->refEntity; - offset[0] = 8; - offset[1] = 0; - offset[2] = 24; + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + cg_railTrailTime.value; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - VectorAdd( cent->lerpOrigin, xoffset, re->origin ); - VectorCopy( re->origin, le->pos.trBase ); - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_RAIL_RINGS; + re->customShader = cgs.media.railRingsShader; - 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 ); + VectorCopy( start, re->origin ); + VectorCopy( end, re->oldorigin ); - AxisCopy( axisDefault, re->axis ); - re->hModel = cgs.media.shotgunBrassModel; - le->bounceFactor = 0.3f; + // nudge down a bit so it isn't exactly in center + re->origin[2] -= 8; + re->oldorigin[2] -= 8; - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 1; - le->angles.trDelta[1] = 0.5; - le->angles.trDelta[2] = 0; + // BFP - No color1 +#if 0 + le->color[0] = ci->color1[0] * 0.75; + le->color[1] = ci->color1[1] * 0.75; + le->color[2] = ci->color1[2] * 0.75; +#endif + le->color[0] = 0.75; + le->color[1] = 0; + le->color[2] = 0; + le->color[3] = 1.0f; - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; - } -} + AxisClear( re->axis ); -/* -========================== -CG_RailTrail -========================== -*/ -void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { - vec3_t axis[36], move, move2, next_move, vec, temp; - float len; - int i, j, skip; - - localEntity_t *le; - refEntity_t *re; - -#define RADIUS 4 -#define ROTATION 1 -#define SPACING 5 - - start[2] -= 4; - VectorCopy (start, move); - VectorSubtract (end, start, vec); - len = VectorNormalize (vec); - PerpendicularVector(temp, vec); - for (i = 0 ; i < 36; i++) { - RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 - } - + // + // core + // 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); - + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + re->shaderTime = cg.time / 1000.0f; re->reType = RT_RAIL_CORE; re->customShader = cgs.media.railCoreShader; - - VectorCopy(start, re->origin); - VectorCopy(end, re->oldorigin); - - re->shaderRGBA[0] = ci->color1[0] * 255; - re->shaderRGBA[1] = ci->color1[1] * 255; - re->shaderRGBA[2] = ci->color1[2] * 255; - re->shaderRGBA[3] = 255; + VectorCopy( start, re->origin ); + VectorCopy( end, re->oldorigin ); + + // nudge down a bit so it isn't exactly in center + re->origin[2] -= 8; + re->oldorigin[2] -= 8; + + // BFP - No color1 +#if 0 le->color[0] = ci->color1[0] * 0.75; le->color[1] = ci->color1[1] * 0.75; le->color[2] = ci->color1[2] * 0.75; +#endif + le->color[0] = 0.75; + le->color[1] = 0; + le->color[2] = 0; le->color[3] = 1.0f; AxisClear( re->axis ); - - VectorMA(move, 20, vec, move); - VectorCopy(move, next_move); - VectorScale (vec, SPACING, vec); - - if (cg_oldRail.integer != 0) { - // nudge down a bit so it isn't exactly in center - re->origin[2] -= 8; - re->oldorigin[2] -= 8; - return; - } - skip = -1; - - j = 18; - for (i = 0; i < len; i += SPACING) { - if (i != skip) { - skip = i + SPACING; - le = CG_AllocLocalEntity(); - re = &le->refEntity; - le->leFlags = LEF_PUFF_DONT_SCALE; - le->leType = LE_MOVE_SCALE_FADE; - le->startTime = cg.time; - le->endTime = cg.time + (i>>1) + 600; - le->lifeRate = 1.0 / (le->endTime - le->startTime); - - re->shaderTime = cg.time / 1000.0f; - re->reType = RT_SPRITE; - re->radius = 1.1f; - re->customShader = cgs.media.railRingsShader; - - re->shaderRGBA[0] = ci->color2[0] * 255; - re->shaderRGBA[1] = ci->color2[1] * 255; - re->shaderRGBA[2] = ci->color2[2] * 255; - re->shaderRGBA[3] = 255; - - le->color[0] = ci->color2[0] * 0.75; - le->color[1] = ci->color2[1] * 0.75; - le->color[2] = ci->color2[2] * 0.75; - le->color[3] = 1.0f; - - le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time; - - VectorCopy( move, move2); - VectorMA(move2, RADIUS , axis[j], move2); - VectorCopy(move2, le->pos.trBase); - - le->pos.trDelta[0] = axis[j][0]*6; - le->pos.trDelta[1] = axis[j][1]*6; - le->pos.trDelta[2] = axis[j][2]*6; - } - - VectorAdd (move, vec, move); - - j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36; - } } /* @@ -288,18 +214,18 @@ CG_RocketTrail ========================== */ static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { - int step; vec3_t origin, lastPos; - int t; - int startTime, contents; - int lastContents; + int lastContents, contents; entityState_t *es; + + // BFP - NOTE: cg_oldRocket is for these who wanna to play Q3 rocket smoke trails + + int step; + int t; + int startTime; vec3_t up; localEntity_t *smoke; - - if ( cg_noProjectileTrail.integer ) { - return; - } + vec3_t color = {1, 0.75, 0}; // BFP - Color for missile trail up[0] = 0; up[1] = 0; @@ -307,10 +233,15 @@ static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { step = 50; - es = &ent->currentState; startTime = ent->trailTime; t = step * ( (startTime + step) / step ); + if ( cg_noProjectileTrail.integer ) { + return; + } + + es = &ent->currentState; + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); contents = CG_PointContents( origin, -1 ); @@ -323,35 +254,48 @@ static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); lastContents = CG_PointContents( lastPos, -1 ); - ent->trailTime = cg.time; + // BFP - Missile trail + if ( cg_oldRocket.integer <= 0 ) { + CG_MissileTrail( ent->currentState.number, origin, cgs.media.railCoreShader, color, qfalse ); + } if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - if ( contents & lastContents & CONTENTS_WATER ) { - CG_BubbleTrail( lastPos, origin, 8 ); + if ( ( contents & lastContents & CONTENTS_WATER ) + && cg.frametime > 0.0f ) { // BFP - If paused, don't spawn bubble particles (cg_paused.integer < 1 is another solution, but not good enough for server responses) + // BFP - Apply particle bubble effect in that case + CG_ParticleBubble( ent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, origin, lastPos, 900, 10, 2 ); + CG_ParticleBubble( ent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, origin, lastPos, 900, 10, 2 ); + CG_ParticleBubble( ent, cgs.media.waterBubbleShader, cgs.media.lowPolySphereModel, origin, lastPos, 900, 10, 2 ); + // CG_BubbleTrail( lastPos, origin, 8 ); } return; } - for ( ; t <= ent->trailTime ; t += step ) { - BG_EvaluateTrajectory( &es->pos, t, lastPos ); + if ( cg_oldRocket.integer > 0 ) { + ent->trailTime = cg.time; - smoke = CG_SmokePuff( lastPos, up, - wi->trailRadius, - 1, 1, 1, 0.33f, - wi->wiTrailTime, - t, - 0, - 0, - cgs.media.smokePuffShader ); - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } } - } +// BFP - Unused CG_PlasmaTrail function +#if 0 /* ========================== -CG_NailTrail +CG_PlasmaTrail ========================== */ static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { @@ -361,19 +305,20 @@ static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { vec3_t velocity, xvelocity, origin; vec3_t offset, xoffset; vec3_t v[3]; - int t, startTime, step; + // BFP - Unused in Q3? What? + // int t, startTime, step; float waterScale = 1.0f; - if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { + if ( cg_noProjectileTrail.integer ) { return; } - step = 50; + // step = 50; es = ¢->currentState; - startTime = cent->trailTime; - t = step * ( (startTime + step) / step ); + // startTime = cent->trailTime; + // t = step * ( (startTime + step) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin ); @@ -444,6 +389,8 @@ static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { le->angles.trDelta[2] = 0; } +#endif + /* ========================== CG_GrappleTrail @@ -452,7 +399,6 @@ CG_GrappleTrail void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { vec3_t origin; entityState_t *es; - vec3_t forward, up; refEntity_t beam; es = &ent->currentState; @@ -461,11 +407,8 @@ void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { ent->trailTime = cg.time; memset( &beam, 0, sizeof( beam ) ); - //FIXME adjust for muzzle position - VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); - beam.origin[2] += 26; - AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); - VectorMA( beam.origin, -6, up, beam.origin ); + + VectorCopy( cg_entities[ es->otherEntityNum ].pe.muzzleOrigin, beam.origin ); VectorCopy( origin, beam.oldorigin ); if (Distance( beam.origin, beam.oldorigin ) < 64 ) @@ -482,6 +425,53 @@ void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { trap_R_AddRefEntityToScene( &beam ); } + +/* +========================== +CG_BFPBeamTrail +========================== +*/ +void CG_BFPBeamTrail( centity_t *ent, const weaponInfo_t *wi ) { // BFP - BFP Beam trail handling + vec3_t origin; + entityState_t *es; + + es = &ent->currentState; + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + // ent->trailTime = cg.time; + + // BFP - NOTE: That's where we apply the flash properties read from client cfg + CG_AddFlashMissile( qfalse, ent, es->otherEntityNum, cg_entities[ es->otherEntityNum ].pe.muzzleOrigin, NULL, "", "ImpactBeamFlashShader", 0, 25, 1 ); + + CG_AddFlashMissile( qtrue, ent, es->number, origin, NULL, "", "ImpactBeamFlashShader", 0, 50, 1 ); + + CG_BeamTrail( es->number, origin, cg_entities[ es->otherEntityNum ].pe.muzzleOrigin, cgs.media.PowerWaveBeamShader ); + // to test corkscrew + // CG_CorkscrewTrail( es->number, origin, cg_entities[ es->otherEntityNum ].pe.muzzleOrigin, cgs.media.SSBBeamShader, cgs.media.SSBSpiralShader ); +} + +/* +========================== +CG_BFPSpiralBeamTrail +========================== +*/ +void CG_BFPSpiralBeamTrail( centity_t *ent, const weaponInfo_t *wi ) { // BFP - BFP Spiral beam trail handling + vec3_t origin; + entityState_t *es; + + es = &ent->currentState; + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + // ent->trailTime = cg.time; + + // BFP - NOTE: That's where we apply the flash properties read from client cfg + CG_AddFlashMissile( qfalse, ent, es->otherEntityNum, cg_entities[ es->otherEntityNum ].pe.muzzleOrigin, NULL, "", "ImpactBeamFlashShader", 0, 25, 1 ); + + CG_AddFlashMissile( qtrue, ent, es->number, origin, NULL, "", "ImpactBeamFlashShader", 0, 50, 1 ); + + CG_CorkscrewTrail( es->number, origin, cg_entities[ es->otherEntityNum ].pe.muzzleOrigin, cgs.media.SSBBeamShader, cgs.media.SSBSpiralShader ); +} + /* ========================== CG_GrenadeTrail @@ -502,9 +492,10 @@ The server says this item is used on this level void CG_RegisterWeapon( int weaponNum ) { weaponInfo_t *weaponInfo; gitem_t *item, *ammo; - char path[MAX_QPATH]; - vec3_t mins, maxs; - int i; + // BFP - Unused variables + //char path[MAX_QPATH]; + //vec3_t mins, maxs; + //int i; weaponInfo = &cg_weapons[weaponNum]; @@ -528,8 +519,11 @@ void CG_RegisterWeapon( int weaponNum ) { if ( !item->classname ) { CG_Error( "Couldn't find weapon %i", weaponNum ); } - CG_RegisterItemVisuals( item - bg_itemlist ); + // BFP - Don't register item visuals again + // CG_RegisterItemVisuals( item - bg_itemlist ); + // BFP - No weaponModel +#if 0 // load cmodel before model so filecache works weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); @@ -538,9 +532,10 @@ void CG_RegisterWeapon( int weaponNum ) { for ( i = 0 ; i < 3 ; i++ ) { weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); } +#endif weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); - weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); + //weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); // BFP - No ammoIcon for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { @@ -551,40 +546,61 @@ void CG_RegisterWeapon( int weaponNum ) { weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); } - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_flash.md3" ); + // BFP - No handsModel, weaponModel and barrelModel +#if 0 + COM_StripExtension( item->world_model[0], path, sizeof(path) ); + Q_strcat( path, sizeof(path), "_flash.md3" ); weaponInfo->flashModel = trap_R_RegisterModel( path ); - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_barrel.md3" ); + COM_StripExtension( item->world_model[0], path, sizeof(path) ); + Q_strcat( path, sizeof(path), "_barrel.md3" ); weaponInfo->barrelModel = trap_R_RegisterModel( path ); - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_hand.md3" ); + COM_StripExtension( item->world_model[0], path, sizeof(path) ); + Q_strcat( path, sizeof(path), "_hand.md3" ); weaponInfo->handsModel = trap_R_RegisterModel( path ); if ( !weaponInfo->handsModel ) { weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); } +#endif weaponInfo->loopFireSound = qfalse; + // BFP - missileTrailFunc + switch ( weaponInfo->missileTrailFuncType ) { + case MISSILE_TRAIL_FUNC_BEAM: // "beam" + weaponInfo->missileTrailFunc = CG_BFPBeamTrail; + break; + case MISSILE_TRAIL_FUNC_ROCKET: // "rocket" + weaponInfo->missileTrailFunc = CG_RocketTrail; + break; + case MISSILE_TRAIL_FUNC_SPIRALBEAM: // "spiralbeam" + weaponInfo->missileTrailFunc = CG_BFPSpiralBeamTrail; + default: // "none" + break; + } + switch ( weaponNum ) { case WP_GAUNTLET: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); + // BFP - TODO: Maybe hitscan weapons use firingSound only, it has some firing sound at the beginning + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + // weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); + + // BFP - constantFireAttack + weaponInfo->constantFireAttack = qtrue; break; case WP_LIGHTNING: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse ); + + // BFP - constantFireAttack + weaponInfo->constantFireAttack = qtrue; + cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); @@ -595,14 +611,27 @@ void CG_RegisterWeapon( int weaponNum ) { case WP_GRAPPLING_HOOK: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); - weaponInfo->missileTrailFunc = CG_GrappleTrail; + // BFP - No missile model + weaponInfo->missileModel = 0; // trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); + weaponInfo->missileTrailFunc = CG_BFPBeamTrail; // BFP - Use BFP Beam instead CG_GrappleTrail weaponInfo->missileDlight = 200; weaponInfo->wiTrailTime = 2000; weaponInfo->trailRadius = 64; MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); + cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew" ); weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); + + // BFP - chargeSound + weaponInfo->chargeSound = cgs.media.defaultKiChargingSound; + + // BFP - missileSound + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + + // BFP - firingSound + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + + // BFP - flashSound + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/bfp/beamflash1.wav", qfalse ); break; case WP_MACHINEGUN: @@ -611,14 +640,19 @@ void CG_RegisterWeapon( int weaponNum ) { weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); break; case WP_SHOTGUN: - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; + // BFP - No flash light, just force field test + //MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + //weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); + + // BFP - noExplosion test + weaponInfo->noExplosion = qtrue; + + // BFP - firingSound + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); break; case WP_ROCKET_LAUNCHER: @@ -648,10 +682,26 @@ void CG_RegisterWeapon( int weaponNum ) { case WP_PLASMAGUN: // weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; - weaponInfo->missileTrailFunc = CG_PlasmaTrail; + // BFP - No plasma trail (cg_oldPlasma > 0) + // weaponInfo->missileTrailFunc = CG_PlasmaTrail; weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); + + // plasmagun dlight + weaponInfo->missileDlight = 200; + MAKERGB( weaponInfo->missileDlightColor, 0.2f, 0.2f, 1.0f ); + + // BFP - missileRotation + weaponInfo->missileRotation = 15; + + // BFP - chargeSound + weaponInfo->chargeSound = cgs.media.defaultKiChargingSound; + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); + + // BFP - missileShader + weaponInfo->missileShader = cgs.media.plasmaBallShader; + cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); break; @@ -667,11 +717,27 @@ void CG_RegisterWeapon( int weaponNum ) { case WP_BFG: weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); + + // BFP - TODO: From skin config use like: + //missileDlight 3 200 + //missileDlightColor 3 0.75 0 1 + + // bfg dlight + weaponInfo->missileDlight = 200; + //MAKERGB( weaponInfo->missileDlightColor, 0.2f, 1.0f, 0.2f ); + MAKERGB( weaponInfo->missileDlightColor, 0.75, 0, 1 ); + + MAKERGB( weaponInfo->flashDlightColor, 1.0f, 0.7f, 1.0f ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); - weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); - weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + + // BFP - Charge sound + weaponInfo->chargeSound = cgs.media.diskKiChargingSound; + + // BFP - Modified for disk weapon testing + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/disk.md3" ); //trap_R_RegisterModel( "models/weaphits/bfg.md3" ); + weaponInfo->missileShader = trap_R_RegisterShader( "purpleAttackShader" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/bfp/diskfly.wav", qfalse ); //trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); break; default: @@ -734,8 +800,8 @@ VIEW WEAPON ======================================================================================== */ -// BFP - TODO: Change animations for BFP - +// BFP - No CG_MapTorsoToWeaponFrame and CG_CalculateWeaponPosition +#if 0 /* ================= CG_MapTorsoToWeaponFrame @@ -744,7 +810,7 @@ CG_MapTorsoToWeaponFrame */ static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { - // BFP - TODO: These animations aren't definitive + // BFP - That was changed for the animations, but in that case, it isn't necessary // change weapon // BFP doesn't use this animation @@ -820,6 +886,7 @@ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { angles[YAW] += scale * fracsin * 0.01; angles[PITCH] += scale * fracsin * 0.01; } +#endif /* @@ -838,6 +905,12 @@ static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { refEntity_t beam; vec3_t forward; vec3_t muzzlePoint, endPoint; + // BFP - lightningBolt config property + qboolean lightningBolt = qtrue; + + if ( !lightningBolt ) { + return; + } if (cent->currentState.weapon != WP_LIGHTNING) { return; @@ -845,6 +918,8 @@ static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { memset( &beam, 0, sizeof( beam ) ); + // BFP - Unused true lightning logic effect, remove in the future +#if 0 // CPMA "true" lightning if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { vec3_t angle; @@ -871,7 +946,9 @@ static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { AngleVectors(angle, forward, NULL, NULL ); VectorCopy(cent->lerpOrigin, muzzlePoint ); // VectorCopy(cg.refdef.vieworg, muzzlePoint ); - } else { + } else +#endif + { // !CPMA AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorCopy(cent->lerpOrigin, muzzlePoint ); @@ -921,69 +998,7 @@ static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { trap_R_AddRefEntityToScene( &beam ); } } -/* - -static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { - trace_t trace; - refEntity_t beam; - vec3_t forward; - vec3_t muzzlePoint, endPoint; - - if ( cent->currentState.weapon != WP_LIGHTNING ) { - return; - } - - memset( &beam, 0, sizeof( beam ) ); - - // find muzzle point for this frame - VectorCopy( cent->lerpOrigin, muzzlePoint ); - AngleVectors( cent->lerpAngles, forward, NULL, NULL ); - - // FIXME: crouch - muzzlePoint[2] += DEFAULT_VIEWHEIGHT; - - VectorMA( muzzlePoint, 14, forward, muzzlePoint ); - - // project forward by the lightning range - VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); - - // see if it hit a wall - CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, - cent->currentState.number, MASK_SHOT ); - // this is the endpoint - VectorCopy( trace.endpos, beam.oldorigin ); - - // use the provided origin, even though it may be slightly - // different than the muzzle origin - VectorCopy( origin, beam.origin ); - - beam.reType = RT_LIGHTNING; - beam.customShader = cgs.media.lightningShader; - trap_R_AddRefEntityToScene( &beam ); - - // add the impact flare if it hit something - if ( trace.fraction < 1.0 ) { - vec3_t angles; - vec3_t dir; - - VectorSubtract( beam.oldorigin, beam.origin, dir ); - VectorNormalize( dir ); - - memset( &beam, 0, sizeof( beam ) ); - beam.hModel = cgs.media.lightningExplosionModel; - - VectorMA( trace.endpos, -16, dir, beam.origin ); - - // make a random orientation - angles[0] = rand() % 360; - angles[1] = rand() % 360; - angles[2] = rand() % 360; - AnglesToAxis( angles, beam.axis ); - trap_R_AddRefEntityToScene( &beam ); - } -} -*/ /* =============== @@ -1008,65 +1023,6 @@ static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) { } -/* -====================== -CG_MachinegunSpinAngle -====================== -*/ -#define SPIN_SPEED 0.9 -#define COAST_TIME 1000 -static float CG_MachinegunSpinAngle( centity_t *cent ) { - int delta; - float angle; - float speed; - - delta = cg.time - cent->pe.barrelTime; - if ( cent->pe.barrelSpinning ) { - angle = cent->pe.barrelAngle + delta * SPIN_SPEED; - } else { - if ( delta > COAST_TIME ) { - delta = COAST_TIME; - } - - speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); - angle = cent->pe.barrelAngle + delta * speed; - } - - 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_AddWeaponWithPowerups -======================== -*/ -static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { - // add powerup effects - if ( powerups & ( 1 << PW_INVIS ) ) { - gun->customShader = cgs.media.invisShader; - trap_R_AddRefEntityToScene( gun ); - } else { - trap_R_AddRefEntityToScene( gun ); - - if ( powerups & ( 1 << PW_BATTLESUIT ) ) { - gun->customShader = cgs.media.battleWeaponShader; - trap_R_AddRefEntityToScene( gun ); - } - if ( powerups & ( 1 << PW_QUAD ) ) { - gun->customShader = cgs.media.quadWeaponShader; - trap_R_AddRefEntityToScene( gun ); - } - } -} - - /* ============= CG_AddPlayerWeapon @@ -1076,84 +1032,59 @@ The main player will have this called for BOTH cases, so effects like light and sound should only be done on the world model case. ============= */ -void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { - refEntity_t gun; - refEntity_t barrel; - refEntity_t flash; - vec3_t angles; +// BFP - CG_AddPlayerWeapon now has a new argument called tagName to attach the flash to a tag +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team, char *tagName ) { weapon_t weaponNum; weaponInfo_t *weapon; centity_t *nonPredictedCent; -// int col; weaponNum = cent->currentState.weapon; CG_RegisterWeapon( weaponNum ); weapon = &cg_weapons[weaponNum]; - // add the weapon - memset( &gun, 0, sizeof( gun ) ); - VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); - gun.shadowPlane = parent->shadowPlane; - gun.renderfx = parent->renderfx; - - // set custom shading for railgun refire rate - if ( ps ) { - if ( cg.predictedPlayerState.weapon == WP_RAILGUN - && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { - float f; - - f = (float)cg.predictedPlayerState.weaponTime / 1500; - gun.shaderRGBA[1] = 0; - gun.shaderRGBA[0] = - gun.shaderRGBA[2] = 255 * ( 1.0 - f ); - } else { - gun.shaderRGBA[0] = 255; - gun.shaderRGBA[1] = 255; - gun.shaderRGBA[2] = 255; - gun.shaderRGBA[3] = 255; + if ( !ps ) { + // BFP - With constantFireAttack, don't play and start the sound looply + if ( !( cent->currentState.eFlags & EF_FIRING ) ) { + cent->pe.constantFireAtkPlayed = qfalse; } - } - // gun.hModel = weapon->weaponModel; // BFP - Hide weapon model - if (!gun.hModel) { - return; - } + // BFP - attackChargeVoice [attack index] [charge count] ["path of sound"] + weapon->attackChargeVoice = trap_S_RegisterSound( "sound/player/bfp1-kyah/attack4V1.wav", qtrue ); + if ( cg_stfu.integer <= 0 + && weapon->attackChargeVoice + && !cent->pe.lastChargeVoiceLevel + && cent->currentState.generic1 == 2 // BFP - TODO: [charge count] set here + && ( ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK0_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK1_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK2_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK3_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK4_PREPARE ) ) { + trap_S_StartSound( cent->lerpOrigin, ENTITYNUM_WORLD, CHAN_VOICE, weapon->attackChargeVoice ); + cent->pe.lastChargeVoiceLevel = qtrue; + } else if ( !( ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK0_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK1_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK2_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK3_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK4_PREPARE ) ) { // stopped charging ki attack, resets to the next cycle + cent->pe.lastChargeVoiceLevel = qfalse; + } - if ( !ps ) { - // add weapon ready sound - cent->pe.lightningFiring = qfalse; - if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { + // BFP - chargeSound + if ( weapon->chargeSound + && ( ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK0_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK1_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK2_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK3_PREPARE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == TORSO_ATTACK4_PREPARE ) ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, + vec3_origin, weapon->chargeSound ); + } else if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { // lightning gun and guantlet make a different sound when fire is held down trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); - cent->pe.lightningFiring = qtrue; - } else if ( weapon->readySound ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } } - CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); - - CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); - - // add the spinning barrel - if ( weapon->barrelModel ) { - memset( &barrel, 0, sizeof( barrel ) ); - VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); - barrel.shadowPlane = parent->shadowPlane; - barrel.renderfx = parent->renderfx; - - barrel.hModel = weapon->barrelModel; - angles[YAW] = 0; - angles[PITCH] = 0; - angles[ROLL] = CG_MachinegunSpinAngle( cent ); - AnglesToAxis( angles, barrel.axis ); - - CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); - - CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); - } - // make sure we aren't looking at cg.predictedPlayerEntity for LG nonPredictedCent = &cg_entities[cent->currentState.clientNum]; @@ -1165,54 +1096,63 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent } // add the flash - if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) + // BFP - constantFireAttack + if ( ( weapon->constantFireAttack || weaponNum == WP_GRAPPLING_HOOK ) && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) { // continuous flash } else { // impulse flash - if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash ) { + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash + && weaponNum != WP_GRAPPLING_HOOK + && weaponNum != WP_PLASMAGUN + && weaponNum != WP_BFG ) { return; } } - memset( &flash, 0, sizeof( flash ) ); - VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); - flash.shadowPlane = parent->shadowPlane; - flash.renderfx = parent->renderfx; - - flash.hModel = weapon->flashModel; - if (!flash.hModel) { - return; - } - angles[YAW] = 0; - angles[PITCH] = 0; - angles[ROLL] = crandom() * 10; - AnglesToAxis( angles, flash.axis ); - - // colorize the railgun blast - if ( weaponNum == WP_RAILGUN ) { - clientInfo_t *ci; - - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - flash.shaderRGBA[0] = 255 * ci->color1[0]; - flash.shaderRGBA[1] = 255 * ci->color1[1]; - flash.shaderRGBA[2] = 255 * ci->color1[2]; + // BFP - NOTE: Here's where the player gets the muzzle attached from some of the tags (apply that to client cfg side) (tag_left, tag_right, tag_eyes, ...) + { + refEntity_t tagEnt; + memset( &tagEnt, 0, sizeof( tagEnt ) ); + CG_PositionEntityOnTag( &tagEnt, parent, parent->hModel, tagName ); + VectorCopy( tagEnt.origin, nonPredictedCent->pe.muzzleOrigin ); + + // BFP - Displaying the muzzle flash to the other player correctly + if ( nonPredictedCent->currentState.eFlags & EF_READY_KI_ATTACK ) { + // BFP - NOTE: That's where we apply the flash properties read from client cfg + if ( weaponNum == WP_GRAPPLING_HOOK ) { + CG_AddFlashMissile( qfalse, nonPredictedCent, -1, nonPredictedCent->pe.muzzleOrigin, &tagEnt, tagName, "ImpactBeamFlashShader", 0, 15, 1 ); + } + if ( weaponNum == WP_PLASMAGUN ) { + CG_AddFlashMissile( qfalse, nonPredictedCent, -1, nonPredictedCent->pe.muzzleOrigin, &tagEnt, tagName, "plasmaExplosion", 0, 15, 1 ); + } + if ( weaponNum == WP_BFG ) { + CG_AddFlashMissile( qfalse, nonPredictedCent, -1, nonPredictedCent->pe.muzzleOrigin, &tagEnt, tagName, "purpleAttackShader", "models/weaphits/disk.md3", 0, 0.8 ); + } + } } - CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); - trap_R_AddRefEntityToScene( &flash ); - if ( ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum ) { + + // BFP - NOTE: That avoids adding the muzzle light using the beam, + // it would be cool adding a light to the player while charging or firing their ki, + // but in a custom way + if ( weaponNum == WP_GRAPPLING_HOOK + || weaponNum == WP_PLASMAGUN + || weaponNum == WP_BFG ) { + return; + } + // add lightning bolt - CG_LightningBolt( nonPredictedCent, flash.origin ); + CG_LightningBolt( nonPredictedCent, parent->origin ); // add rail trail - CG_SpawnRailTrail( cent, flash.origin ); + CG_SpawnRailTrail( cent, parent->origin ); if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { - trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0], + trap_R_AddLightToScene( parent->origin, 300 + (rand()&31), weapon->flashDlightColor[0], weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); } } @@ -1226,12 +1166,13 @@ Add the weapon, and flash for the player's view ============== */ void CG_AddViewWeapon( playerState_t *ps ) { - refEntity_t hand; - centity_t *cent; - clientInfo_t *ci; - float fovOffset; - vec3_t angles; - weaponInfo_t *weapon; + // BFP - Unused variables + //refEntity_t hand; + //clientInfo_t *ci; + //vec3_t angles; + //weaponInfo_t *weapon; + //centity_t *cent; + //float fovOffset; if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { return; @@ -1266,15 +1207,20 @@ void CG_AddViewWeapon( playerState_t *ps ) { return; } + // BFP - No fovOffset +#if 0 // drop gun lower at higher fov if ( cg_fov.integer > 90 ) { fovOffset = -0.2 * ( cg_fov.integer - 90 ); } else { fovOffset = 0; } +#endif - cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; + //cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; CG_RegisterWeapon( ps->weapon ); + // BFP - No draw hand and gun +#if 0 weapon = &cg_weapons[ ps->weapon ]; memset (&hand, 0, sizeof(hand)); @@ -1305,7 +1251,8 @@ void CG_AddViewWeapon( playerState_t *ps ) { hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; // add everything onto the hand - CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); + CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM], "" ); +#endif } /* @@ -1321,7 +1268,8 @@ WEAPON SELECTION CG_DrawWeaponSelect =================== */ -void CG_DrawWeaponSelect( void ) { +void CG_DrawWeaponSelect( void ) { // BFP - Modified Q3 selectable weapon HUD + // BFP - TODO: Change that selection to 5 selectable ki attacks int i; int bits; int count; @@ -1344,18 +1292,20 @@ void CG_DrawWeaponSelect( void ) { cg.itemPickupTime = 0; // count the number of weapons owned + i = 0; bits = cg.snap->ps.stats[ STAT_WEAPONS ]; count = 0; - for ( i = 1 ; i < 16 ; i++ ) { + while ( ++i < 16 ) { if ( bits & ( 1 << i ) ) { - count++; + ++count; } } - x = 320 - count * 20; - y = 380; + x = 10; + y = 75; - for ( i = 1 ; i < 16 ; i++ ) { + i = 0; + while ( ++i < 16 ) { if ( !( bits & ( 1 << i ) ) ) { continue; } @@ -1363,28 +1313,30 @@ void CG_DrawWeaponSelect( void ) { CG_RegisterWeapon( i ); // draw weapon icon - CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); + CG_DrawPic( x, y, 91, 46, cg_weapons[i].weaponIcon ); // draw selection marker if ( i == cg.weaponSelect ) { - CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); + CG_DrawPic( x, y, 91, 46, cgs.media.selectShader ); } // no ammo cross on top if ( !cg.snap->ps.ammo[ i ] ) { - CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); + CG_DrawPic( x, y, 91, 46, cgs.media.noammoShader ); } - x += 40; + y += 47; } + // BFP - attackName + // draw the selected name if ( cg_weapons[ cg.weaponSelect ].item ) { name = cg_weapons[ cg.weaponSelect ].item->pickup_name; if ( name ) { w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; - CG_DrawBigStringColor(x, y - 22, name, color); + CG_DrawBigStringColor(x, 288, name, color); } } @@ -1432,9 +1384,12 @@ void CG_NextWeapon_f( void ) { if ( cg.weaponSelect == 16 ) { cg.weaponSelect = 0; } + // BFP - Don't lock the scroll to this weapon index +#if 0 if ( cg.weaponSelect == WP_GAUNTLET ) { continue; // never cycle to gauntlet } +#endif if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } @@ -1468,9 +1423,12 @@ void CG_PrevWeapon_f( void ) { if ( cg.weaponSelect == -1 ) { cg.weaponSelect = 15; } + // BFP - Don't lock the scroll to this weapon index +#if 0 if ( cg.weaponSelect == WP_GAUNTLET ) { continue; // never cycle to gauntlet } +#endif if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } @@ -1566,11 +1524,54 @@ void CG_FireWeapon( centity_t *cent ) { // append the flash to the weapon model cent->muzzleFlashTime = cg.time; - // lightning gun only does this this on initial press - if ( ent->weapon == WP_LIGHTNING ) { - if ( cent->pe.lightningFiring ) { - return; + // BFP - constantFireAttack + if ( weap->constantFireAttack ) { + if ( cent->pe.constantFireAtkPlayed ) { + goto _skipFlashSound; } + cent->pe.constantFireAtkPlayed = qtrue; + } + + // BFP - attackFireVoice + weap->attackFireVoice = trap_S_RegisterSound( "sound/player/bfp1-kyah/attack5F.wav", qtrue ); + if ( cg_stfu.integer <= 0 + && weap->attackFireVoice && ( cent->currentState.eFlags & EF_FIRING ) + && ent->weapon != WP_SHOTGUN ) { // BFP - TODO: Handle forcefield + trap_S_StartSound( cent->lerpOrigin, ent->number, CHAN_VOICE, weap->attackFireVoice ); + } + + // BFP - Just testing, WP_SHOTGUN treated as ki explosion example + if ( ent->weapon == WP_SHOTGUN ) { + cent->pe.chargeAutoFire = qtrue; + } + if ( ent->weapon == WP_SHOTGUN && !cent->pe.chargeAutoFire + && weap->noExplosion ) { + // BFP - Use that as blinding_flash weapon, no chargeAutoFire set + // this is when noExplosion is set as weapon config dictates + + // BFP - Low poly sphere + // BFP - TODO: Apply explosionModel from bfp attack config, highPolySphereModel is just a test + qhandle_t sphereModel = ( cg_lowpolysphere.integer > 0 ) ? cgs.media.lowPolySphereModel : cgs.media.highPolySphereModel; + qhandle_t blindingShader = trap_R_RegisterShader( "BlindingFlashShader" ); + localEntity_t *leSphere; + const float MAX_SCALE = 27, MAX_SCALEFACTOR = 6.0f; // limits to prevent too large scaling + // BFP - TODO: Apply explosionScaleFactor as indicated on default.cfg file from some character: explosionScaleFactor + // BFP - TODO: Apply explosionScaleFactorChargeMult as indicated on default.cfg file from some character: explosionScaleFactorChargeMult + float explosionScaleFactor = 3, explosionScaleFactorChargeMult = 0; + float scale = 1; + + if ( explosionScaleFactor > MAX_SCALEFACTOR ) explosionScaleFactor = MAX_SCALEFACTOR; + if ( explosionScaleFactorChargeMult > MAX_SCALEFACTOR ) explosionScaleFactorChargeMult = MAX_SCALEFACTOR; + scale = explosionScaleFactor + explosionScaleFactorChargeMult; + if ( scale > MAX_SCALE ) scale = MAX_SCALE; + + leSphere = CG_SpawnExplosionModel( cent->lerpOrigin, NULL, LE_EXPLOSION_SPHERE, sphereModel, blindingShader, 1000 ); + VectorScale( leSphere->refEntity.axis[0], scale, leSphere->refEntity.axis[0] ); + VectorScale( leSphere->refEntity.axis[1], scale, leSphere->refEntity.axis[1] ); + VectorScale( leSphere->refEntity.axis[2], scale, leSphere->refEntity.axis[2] ); + + CG_ExplosionSound( cent->lerpOrigin ); + return; } // play quad sound if needed @@ -1592,13 +1593,15 @@ void CG_FireWeapon( centity_t *cent ) { } } +// BFP - constantFireAttack handling +_skipFlashSound: + // do brass ejection if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { weap->ejectBrassFunc( cent ); } } - /* ================= CG_MissileHitWall @@ -1608,33 +1611,17 @@ Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing */ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { qhandle_t mod; - qhandle_t mark; qhandle_t shader; sfxHandle_t sfx; float radius; - float light; - vec3_t lightColor; - localEntity_t *le; int r; - qboolean alphaFade; - qboolean isSprite; - int duration; - vec3_t sprOrg; - vec3_t sprVel; - - mark = 0; - radius = 32; + + // BFP - NOTE: Crack mark shader replaces all other mark shaders, the radius is the same (64), there's no alpha fade + + radius = 64; sfx = 0; mod = 0; shader = 0; - light = 0; - lightColor[0] = 1; - lightColor[1] = 1; - lightColor[2] = 0; - - // set defaults - isSprite = qfalse; - duration = 600; switch ( weapon ) { default: @@ -1648,71 +1635,40 @@ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, im } else { sfx = cgs.media.sfx_lghit3; } - mark = cgs.media.holeMarkShader; - radius = 12; break; case WP_GRENADE_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.grenadeExplosionShader; sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; break; case WP_ROCKET_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.rocketExplosionShader; sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; - duration = 1000; - lightColor[0] = 1; - lightColor[1] = 0.75; - lightColor[2] = 0.0; - if (cg_oldRocket.integer == 0) { - // explosion sprite animation - VectorMA( origin, 24, dir, sprOrg ); - VectorScale( dir, 64, sprVel ); - - CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); - } break; case WP_RAILGUN: mod = cgs.media.ringFlashModel; shader = cgs.media.railExplosionShader; sfx = cgs.media.sfx_plasmaexp; - mark = cgs.media.energyMarkShader; - radius = 24; break; case WP_PLASMAGUN: mod = cgs.media.ringFlashModel; shader = cgs.media.plasmaExplosionShader; sfx = cgs.media.sfx_plasmaexp; - mark = cgs.media.energyMarkShader; - radius = 16; break; case WP_BFG: mod = cgs.media.dishFlashModel; shader = cgs.media.bfgExplosionShader; sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 32; - isSprite = qtrue; break; case WP_SHOTGUN: mod = cgs.media.bulletFlashModel; shader = cgs.media.bulletExplosionShader; - mark = cgs.media.bulletMarkShader; sfx = 0; - radius = 4; break; case WP_MACHINEGUN: mod = cgs.media.bulletFlashModel; shader = cgs.media.bulletExplosionShader; - mark = cgs.media.bulletMarkShader; r = rand() & 3; if ( r == 0 ) { @@ -1723,42 +1679,57 @@ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, im sfx = cgs.media.sfx_ric3; } - radius = 8; break; } - if ( sfx ) { + // BFP - Don't display for force field weapons + if ( weapon == WP_SHOTGUN ) { + return; + } + + if ( sfx + && ( weapon == WP_MACHINEGUN || weapon == WP_SHOTGUN || weapon == WP_LIGHTNING ) ) { // BFP - TODO: Apply these weapon types into this sound trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } else { // BFP - Explosion sounds + CG_ExplosionSound( origin ); } // // create the explosion // - if ( mod ) { - le = CG_MakeExplosion( origin, dir, - mod, shader, - duration, isSprite ); - le->light = light; - VectorCopy( lightColor, le->lightColor ); - if ( weapon == WP_RAILGUN ) { - // colorize with client color - VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); - } + if ( weapon == WP_MACHINEGUN || weapon == WP_SHOTGUN ) { // BFP - TODO: Apply muzzle effects only to these weapon types (from default.cfg file from some character) + // BFP - Don't use this explosion, use explosion model instead +#if 0 + CG_MakeExplosion( origin, dir, + mod, shader, + 600, qfalse ); +#endif + const float MAX_SCALE = 27, MAX_SCALEFACTOR = 6.0f; // limits to prevent too large scaling + // BFP - TODO: Apply explosionScaleFactor as indicated on default.cfg file from some character: explosionScaleFactor + // BFP - TODO: Apply explosionScaleFactorChargeMult as indicated on default.cfg file from some character: explosionScaleFactorChargeMult + float explosionScaleFactor = 3, explosionScaleFactorChargeMult = 0; + float scale = 1; + localEntity_t *leExp; + + if ( explosionScaleFactor > MAX_SCALEFACTOR ) explosionScaleFactor = MAX_SCALEFACTOR; + if ( explosionScaleFactorChargeMult > MAX_SCALEFACTOR ) explosionScaleFactorChargeMult = MAX_SCALEFACTOR; + scale = explosionScaleFactor + explosionScaleFactorChargeMult; + if ( scale > MAX_SCALE ) scale = MAX_SCALE; + + leExp = CG_SpawnExplosionModel( origin, dir, LE_EXPLOSION_SPHERE, mod, shader, 1200 ); + VectorScale( leExp->refEntity.axis[0], scale, leExp->refEntity.axis[0] ); + VectorScale( leExp->refEntity.axis[1], scale, leExp->refEntity.axis[1] ); + VectorScale( leExp->refEntity.axis[2], scale, leExp->refEntity.axis[2] ); + } else if ( weapon == WP_LIGHTNING ) { // BFP - TODO: Just a test for lightning gun (eyebeam should have this behavior) + CG_SmokeExplosion( origin, dir ); // BFP - Explosion smoke + } else { // BFP - Explosion effects + CG_ExplosionEffect( origin, dir ); } // // impact mark // - alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color - if ( weapon == WP_RAILGUN ) { - float *color; - - // colorize with client color - color = cgs.clientinfo[clientNum].color2; - CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); - } else { - CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); - } + CG_ImpactMark( cgs.media.crackMarkShader, origin, dir, random()*360, 1,1,1,1, 0, radius, qfalse ); } @@ -1768,16 +1739,57 @@ CG_MissileHitPlayer ================= */ void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { - CG_Bleed( origin, entityNum ); + // BFP - Seems like a small redo from CG_MissileHitWall to adjust the needed effects + sfxHandle_t sfx = 0; + + // BFP - NOTE: Originally on BFP, players don't bleed, that's a friendly mod :P + // 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: - CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); + case WP_LIGHTNING: + break; + case WP_MACHINEGUN: + case WP_SHOTGUN: // BFP - TODO: Apply muzzle effects only to these weapon types (from default.cfg file from some character) + if ( weapon == WP_MACHINEGUN ) { + int r = rand() & 3; + switch ( r ) { + case 0: sfx = cgs.media.sfx_ric1; break; + case 1: sfx = cgs.media.sfx_ric2; break; + default: sfx = cgs.media.sfx_ric3; break; + } + } + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + // BFP - Don't use this explosion, use explosion model instead +#if 0 + CG_MakeExplosion( origin, dir, + cgs.media.bulletFlashModel, cgs.media.bulletExplosionShader, + 600, qfalse ); +#endif + { + const float MAX_SCALE = 27, MAX_SCALEFACTOR = 6.0f; // limits to prevent too large scaling + // BFP - TODO: Apply explosionScaleFactor as indicated on default.cfg file from some character: explosionScaleFactor + // BFP - TODO: Apply explosionScaleFactorChargeMult as indicated on default.cfg file from some character: explosionScaleFactorChargeMult + float explosionScaleFactor = 3, explosionScaleFactorChargeMult = 0; + float scale = 1; + localEntity_t *leExp; + + if ( explosionScaleFactor > MAX_SCALEFACTOR ) explosionScaleFactor = MAX_SCALEFACTOR; + if ( explosionScaleFactorChargeMult > MAX_SCALEFACTOR ) explosionScaleFactorChargeMult = MAX_SCALEFACTOR; + scale = explosionScaleFactor + explosionScaleFactorChargeMult; + if ( scale > MAX_SCALE ) scale = MAX_SCALE; + + leExp = CG_SpawnExplosionModel( origin, dir, LE_EXPLOSION_SPHERE, cgs.media.bulletFlashModel, cgs.media.bulletExplosionShader, 1200 ); + VectorScale( leExp->refEntity.axis[0], scale, leExp->refEntity.axis[0] ); + VectorScale( leExp->refEntity.axis[1], scale, leExp->refEntity.axis[1] ); + VectorScale( leExp->refEntity.axis[2], scale, leExp->refEntity.axis[2] ); + } break; default: + CG_ExplosionSound( origin ); // BFP - Explosion sounds + CG_SparksExplosion( origin, dir ); // BFP - Spark particles explosion + CG_ExplosionEffect( origin, dir ); // BFP - Explosion effects break; } } @@ -1799,10 +1811,13 @@ CG_ShotgunPellet */ static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { trace_t tr; - int sourceContentType, destContentType; + // BFP - Unused variables + // int sourceContentType, destContentType; CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); + // BFP - Don't spawn bubble trail effect +#if 0 sourceContentType = trap_CM_PointContents( start, 0 ); destContentType = trap_CM_PointContents( tr.endpos, 0 ); @@ -1822,6 +1837,7 @@ static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); } +#endif if ( tr.surfaceFlags & SURF_NOIMPACT ) { return; @@ -1943,36 +1959,20 @@ void CG_Tracer( vec3_t source, vec3_t dest ) { VectorNormalize( right ); VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); - verts[0].st[0] = 0; - verts[0].st[1] = 1; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; + Vector2Set( verts[0].st, 0, 1 ); + Byte4Set( verts[0].modulate, 255, 255, 255, 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; + Vector2Set( verts[1].st, 1, 0 ); + Byte4Set( verts[1].modulate, 255, 255, 255, 255 ); VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; + Vector2Set( verts[2].st, 1, 1 ); + Byte4Set( verts[2].modulate, 255, 255, 255, 255 ); VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); - verts[3].st[0] = 0; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; + Vector2Set( verts[3].st, 0, 0 ); + Byte4Set( verts[3].modulate, 255, 255, 255, 255 ); trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); @@ -1985,7 +1985,8 @@ void CG_Tracer( vec3_t source, vec3_t dest ) { } - +// BFP - Unused CG_CalcMuzzlePoint function +#if 0 /* ====================== CG_CalcMuzzlePoint @@ -2024,6 +2025,7 @@ static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { return qtrue; } +#endif /* ====================== @@ -2033,6 +2035,8 @@ Renders bullet effects. ====================== */ void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) { + // BFP - No water particles in BFP by firing a kind of ki-type bullet +#if 0 trace_t trace; int sourceContentType, destContentType; vec3_t start; @@ -2065,12 +2069,8 @@ void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, } } } +#endif // impact splash and mark - if ( flesh ) { - CG_Bleed( end, fleshEntityNum ); - } else { - CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); - } - + CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); } diff --git a/source/cgame/cgame.bat b/source/cgame/cgame.bat index 18a79ba..ce03fcb 100644 --- a/source/cgame/cgame.bat +++ b/source/cgame/cgame.bat @@ -40,6 +40,8 @@ set cc=..\..\..\tools\bin\lcc.exe -DQ3_VM -S -Wf-target=bytecode -Wf-g -I%src%\c @if errorlevel 1 goto quit %cc% %src%/cgame/cg_marks.c @if errorlevel 1 goto quit +%cc% %src%/cgame/cg_particles.c +@if errorlevel 1 goto quit %cc% %src%/cgame/cg_players.c @if errorlevel 1 goto quit %cc% %src%/cgame/cg_playerstate.c @@ -52,6 +54,8 @@ set cc=..\..\..\tools\bin\lcc.exe -DQ3_VM -S -Wf-target=bytecode -Wf-g -I%src%\c @if errorlevel 1 goto quit %cc% %src%/cgame/cg_snapshot.c @if errorlevel 1 goto quit +%cc% %src%/cgame/cg_trails.c +@if errorlevel 1 goto quit %cc% %src%/cgame/cg_view.c @if errorlevel 1 goto quit %cc% %src%/cgame/cg_weapons.c diff --git a/source/cgame/cgame.def b/source/cgame/cgame.def new file mode 100644 index 0000000..2ee748e --- /dev/null +++ b/source/cgame/cgame.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/source/cgame/cgame.q3asm b/source/cgame/cgame.q3asm index c44b804..957c050 100644 --- a/source/cgame/cgame.q3asm +++ b/source/cgame/cgame.q3asm @@ -10,12 +10,14 @@ cg_event cg_info cg_localents cg_marks +cg_particles cg_players cg_playerstate cg_predict cg_scoreboard cg_servercmds cg_snapshot +cg_trails cg_view cg_weapons bg_slidemove diff --git a/source/cgame/tr_types.h b/source/cgame/tr_types.h index b3ed8da..232e8db 100644 --- a/source/cgame/tr_types.h +++ b/source/cgame/tr_types.h @@ -148,22 +148,26 @@ typedef enum { TC_S3TC } textureCompression_t; - -// -// glDriverType_t is not used by the engine and quake3 game anymore. -// Single value (GLDRV_ICD) is left only for compatibility with other mods. typedef enum { - GLDRV_ICD // driver is integrated with window system + 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; -// -// -// glHardwareType_t is not used by the engine and quake3 game anymore. -// Single value (GLHW_GENERIC) is left only for compatibility with other mods. typedef enum { - GLHW_GENERIC // where everthing works the way it should + 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]; diff --git a/source/game/ai_bfp.c b/source/game/ai_bfp.c new file mode 100644 index 0000000..3324098 --- /dev/null +++ b/source/game/ai_bfp.c @@ -0,0 +1,900 @@ +/* +=========================================================================== + +BFP BOT AI + +=========================================================================== +*/ + + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_bfp.h" +// +#include "chars.h" +#include "inv.h" +#include "syn.h" +#include "match.h" + +// thresholds for bot behavior +#define BFP_BOT_KI_LOW_PCT 0.057f +#define BFP_BOT_KI_SAFE_PCT 0.705f +#define BFP_BOT_KI_BOOST_MIN_PCT 0.05f +#define BFP_BOT_FLIGHT_RANGE 700.0f +#define BFP_BOT_MELEE_RANGE_PAD 10.0f +#define BFP_BOT_WEAPON_SWITCH_MINTIME 1.0f +#define BFP_BOT_WEAPON_SWITCH_MAXTIME 3.0f +#define BFP_BOT_KI_CHARGE_DANGER_RANGE 100.0f +#define BFP_BOT_MELEE_REPRESS_DELAY 0.25f +#define BFP_BOT_MELEE_STANCE_HOLD 2.0f +#define BFP_BOT_MELEE_STANCE_RANGE_BONUS 40.0f +#define BFP_BOT_CHASE_TRIGGER_RANGE 1520.0f +#define BFP_BOT_CHASE_DURATION 5.25f +#define BFP_BOT_ZANZOKEN_PRESS_MS 120 +#define BFP_BOT_ZANZOKEN_RELEASE_MS 40 +#define BFP_BOT_ZANZOKEN_DANGER_RANGE 300.0f +#define BFP_BOT_ZANZOKEN_HITSTUN_CHANCE 0.55f +#define BFP_BOT_ZANZOKEN_HITSTUN_RETRY_MS 400 // how often, in ms, the bot re-rolls its zanzoken-escape chance while hitstun is active - lets it try more than once during a ~3s stun instead of a single roll at the start +#define BFP_BOT_FIELD_OF_VIEW 180.0f // very visible, even behind +#define BFP_BOT_SIXTHSENSE_RANGE 16000.0f + +static const float bfpFlightChanceBySkill[6] = { + 0.26f, // skill 0 + 0.34f, // skill 1 + 0.42f, // skill 2 + 0.50f, // skill 3 + 0.61f, // skill 4 + 0.74f // skill 5 +}; + +// BFP - TODO: Just a test, replace for bfp_weapon.cfg +static const int bfpAttackSlots[] = { + WP_MACHINEGUN, + WP_SHOTGUN, + WP_GRENADE_LAUNCHER, + WP_ROCKET_LAUNCHER, + WP_GRAPPLING_HOOK +}; +#define BFP_NUM_ATTACK_SLOTS ( sizeof(bfpAttackSlots) / sizeof(bfpAttackSlots[0]) ) + +typedef struct { + int weaponNum; + int attackType; + qboolean chargeAttack; + qboolean chargeAutoFire; +} bfpWeaponProfile_t; + +static const bfpWeaponProfile_t bfpWeaponProfiles[] = { + { WP_MACHINEGUN, ATK_HITSCAN, qfalse, qfalse }, + { WP_SHOTGUN, ATK_FORCEFIELD, qtrue, qtrue }, + { WP_GRENADE_LAUNCHER, ATK_MISSILE, qfalse, qfalse }, + { WP_ROCKET_LAUNCHER, ATK_MISSILE, qfalse, qfalse }, + { WP_GRAPPLING_HOOK, ATK_BEAM, qtrue, qfalse } +}; + +/* +================== +BotBFPGetWeaponProfile +================== +*/ +static const bfpWeaponProfile_t *BotBFPGetWeaponProfile( int weaponNum ) { + int i; + for ( i = 0; i < (int)( sizeof(bfpWeaponProfiles) / sizeof(bfpWeaponProfiles[0]) ); i++ ) { + if ( bfpWeaponProfiles[i].weaponNum == weaponNum ) { + return &bfpWeaponProfiles[i]; + } + } + return NULL; +} + +/* +================== +BotBFPResetState +================== +*/ +void BotBFPResetState( bot_state_t *bs ) { + bs->bfpButtons = 0; + bs->bfpKiRecharging = qfalse; + bs->bfpKiRechargeInterrupted_time = 0; + bs->bfpLastHealth = -1; + bs->bfpLastMelee_time = 0; + bs->bfpStrafeFlip_time = 0; + bs->bfpFlightDecision = qfalse; + bs->bfpFlightReroll_time = 0; + bs->bfpZanzokenPhase = 0; + bs->bfpZanzokenPhaseEnd_time = 0; + bs->bfpZanzokenDir = 0; + bs->bfpZanzokenHitstunRetry_time = 0; + bs->bfpRightmoveOverride = 0; + bs->bfpRightmoveOverrideActive = qfalse; + bs->bfpForceAttackOff = qfalse; + bs->bfpKiCompensate_time = 0; + bs->bfpSixthSenseStep_time = 0; + bs->bfpEvadeTime = 0; + VectorClear( bs->bfpEvadeDir ); + + // ensure a valid weapon is selected even before any combat logic runs + if ( bs->weaponnum <= WP_NONE ) { + bs->weaponnum = WP_GAUNTLET; + } +} + +/* +================== +BotBFPApplyButtons +================== +*/ +void BotBFPApplyButtons( bot_state_t *bs, usercmd_t *ucmd ) { + ucmd->buttons |= bs->bfpButtons; + if ( bs->bfpForceAttackOff ) { + ucmd->buttons &= ~BUTTON_ATTACK; + } + if ( bs->bfpRightmoveOverrideActive ) { + ucmd->rightmove = bs->bfpRightmoveOverride; + } +} + +/* +================== +BotBFPWantsToMelee +================== +*/ +static qboolean BotBFPWantsToMelee( bot_state_t *bs, aas_entityinfo_t *entinfo ) { + float rangeMultiplier, distance; + vec3_t dir; + + if ( bs->cur_ps.stats[STAT_HITSTUN_TIME] > 0 ) { + return qfalse; + } + + VectorSubtract( entinfo->origin, bs->origin, dir ); + distance = VectorLength( dir ); + + rangeMultiplier = g_meleeRange.integer + 45 - BFP_BOT_MELEE_RANGE_PAD; + if ( bs->cur_ps.eFlags & EF_MONSTER ) { + rangeMultiplier *= 2.5f; + } + + if ( bs->bfpLastMelee_time > FloatTime() - BFP_BOT_MELEE_STANCE_HOLD ) { + rangeMultiplier += BFP_BOT_MELEE_STANCE_RANGE_BONUS; + } + + if ( distance > rangeMultiplier ) { + return qfalse; + } + + if ( !BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, BFP_BOT_FIELD_OF_VIEW, bs->enemy ) ) { + return qfalse; + } + + return qtrue; +} + +/* +================== +BotBFPCheckMelee +================== +*/ +static void BotBFPCheckMelee( bot_state_t *bs, aas_entityinfo_t *entinfo ) { + if ( !BotBFPWantsToMelee( bs, entinfo ) ) { + return; + } + if ( bs->bfpLastMelee_time > FloatTime() - BFP_BOT_MELEE_REPRESS_DELAY ) { + return; + } + bs->bfpButtons |= BUTTON_MELEE; + bs->bfpLastMelee_time = FloatTime(); +} + +/* +================== +BotBFPCompensateKiCharge +================== +*/ +static void BotBFPCompensateKiCharge( bot_state_t *bs ) { + gentity_t *botent; + playerState_t *ps; + int maxKi; + const float HUMAN_CMD_INTERVAL = 0.017f; // 17 ms + float now, elapsed, kiPerSecond, kiChargeTotal; + + botent = &g_entities[bs->client]; + if ( !botent->inuse || !botent->client ) { + return; + } + ps = &botent->client->ps; + now = FloatTime(); + + if ( !( ( ps->pm_flags & PMF_KI_CHARGE ) && ( ps->eFlags & EF_AURA ) ) ) { + bs->bfpKiCompensate_time = now; + return; + } + + if ( ps->stats[STAT_HITSTUN_TIME] > 0 ) { + bs->bfpKiCompensate_time = now; + return; + } + + maxKi = ps->stats[STAT_MAX_KI]; + if ( ps->stats[STAT_KI] >= maxKi ) { + bs->bfpKiCompensate_time = now; + return; + } + + if ( bs->bfpKiCompensate_time <= 0 ) { + bs->bfpKiCompensate_time = now; + return; + } + + elapsed = now - bs->bfpKiCompensate_time; + if ( elapsed <= 0 ) { + return; + } + if ( elapsed > 2.0f ) { + elapsed = 2.0f; + } + + kiChargeTotal = ( g_kiCharge.value * 0.01f ) + g_kiChargePct.value * ( maxKi * 0.0001f ); + kiPerSecond = kiChargeTotal / HUMAN_CMD_INTERVAL; + ps->stats[STAT_KI] += (int)( kiPerSecond * elapsed ); + if ( ps->stats[STAT_KI] > maxKi ) { + ps->stats[STAT_KI] = maxKi; + } + + bs->bfpKiCompensate_time = now; +} + +/* +================== +BotBFPCheckKiRecharge +================== +*/ +static void BotBFPCheckKiRecharge( bot_state_t *bs, aas_entityinfo_t *entinfo ) { + int maxKi, curKi, health; + vec3_t dir; + float enemyDist; + qboolean enemyDanger, justHit; + + maxKi = bs->cur_ps.stats[STAT_MAX_KI]; + curKi = bs->cur_ps.stats[STAT_KI]; + health = bs->inventory[INVENTORY_HEALTH]; + + if ( maxKi <= 0 ) { + return; + } + + enemyDanger = qfalse; + if ( entinfo != NULL ) { + VectorSubtract( entinfo->origin, bs->origin, dir ); + enemyDist = VectorLength( dir ); + enemyDanger = ( enemyDist < BFP_BOT_KI_CHARGE_DANGER_RANGE ); + } + + if ( bs->bfpLastHealth < 0 ) { + bs->bfpLastHealth = health; + } + justHit = ( health < bs->bfpLastHealth ); + bs->bfpLastHealth = health; + + if ( ( justHit || enemyDanger ) && bs->bfpKiRecharging ) { + bs->bfpKiRecharging = qfalse; + bs->bfpKiRechargeInterrupted_time = FloatTime(); + return; + } + + if ( bs->bfpKiRechargeInterrupted_time > FloatTime() - 1.5 ) { + return; + } + + if ( enemyDanger ) { + return; + } + + if ( !bs->bfpKiRecharging ) { + if ( curKi < maxKi * BFP_BOT_KI_LOW_PCT ) { + bs->bfpKiRecharging = qtrue; + } + } else { + if ( curKi >= maxKi * BFP_BOT_KI_SAFE_PCT ) { + bs->bfpKiRecharging = qfalse; + } + } + + if ( bs->bfpKiRecharging ) { + bs->bfpButtons |= BUTTON_KI_CHARGE; + } +} + +/* +================== +BotBFPEnemyAirborne +================== +*/ +static qboolean BotBFPEnemyAirborne( aas_entityinfo_t *entinfo ) { + bsp_trace_t trace; + vec3_t end; + + VectorCopy( entinfo->origin, end ); + end[2] -= 48; + BotAI_Trace( &trace, entinfo->origin, entinfo->mins, entinfo->maxs, end, -1, MASK_SOLID ); + return ( trace.fraction >= 1.0 ); +} + +/* +================== +BotBFPCheckFlight +================== +*/ +static void BotBFPCheckFlight( bot_state_t *bs, aas_entityinfo_t *entinfo, bot_goal_t *goal ) { + int skillIndex; + float flightChance, dist, stopChance; + vec3_t dir; + qboolean wantsFlight, alreadyFlying; + + if ( bs->cur_ps.stats[STAT_KI] <= 0 ) { + bs->bfpFlightDecision = qfalse; + return; + } + + if ( bs->cur_ps.pm_flags & PMF_KI_CHARGE ) { + return; + } + + skillIndex = (int)bs->settings.skill; + if ( skillIndex < 0 ) { + skillIndex = 0; + } + if ( skillIndex > 5 ) { + skillIndex = 5; + } + flightChance = bfpFlightChanceBySkill[skillIndex]; + + alreadyFlying = ( bs->cur_ps.eFlags & EF_FLIGHT ); + + VectorSubtract( entinfo->origin, bs->origin, dir ); + dist = VectorLength( dir ); + + wantsFlight = qfalse; + if ( entinfo != NULL ) { // combat + VectorSubtract( entinfo->origin, bs->origin, dir ); + dist = VectorLength( dir ); + if ( BotBFPEnemyAirborne( entinfo ) ) { + wantsFlight = qtrue; + } else if ( dist > BFP_BOT_FLIGHT_RANGE ) { + wantsFlight = qtrue; + } + } else if ( goal != NULL ) { // CTF + VectorSubtract( goal->origin, bs->origin, dir ); + dist = VectorLength( dir ); + if ( dist > BFP_BOT_FLIGHT_RANGE ) { + wantsFlight = qtrue; + } + } else { + return; + } + + if ( bs->bfpFlightReroll_time < FloatTime() ) { + float windowMin = 1.0f + ( skillIndex / 5.0f ) * 1.0f; + float windowMax = 2.0f + ( skillIndex / 5.0f ) * 2.0f; + float window = windowMin + random() * ( windowMax - windowMin ); + + if ( !alreadyFlying ) { + bs->bfpFlightDecision = ( wantsFlight && random() < flightChance ); + } else { + int maxKi = bs->cur_ps.stats[STAT_MAX_KI]; + int curKi = bs->cur_ps.stats[STAT_KI]; + + stopChance = 0.6f - ( (float)skillIndex / 5.0f ) * 0.55f; + if ( stopChance < 0.05f ) { + stopChance = 0.05f; + } + if ( stopChance > 0.6f ) { + stopChance = 0.6f; + } + if ( maxKi > 0 && curKi < maxKi * 0.2f ) { + stopChance += 0.2f; + if ( stopChance > 0.8f ) { + stopChance = 0.8f; + } + } + + if ( wantsFlight ) { + bs->bfpFlightDecision = ( random() > stopChance * 0.2f ); + } else { + bs->bfpFlightDecision = ( random() > stopChance ); + } + } + bs->bfpFlightReroll_time = FloatTime() + window; + } + + if ( bs->bfpFlightDecision != bs->bfpLastFlightDecision ) { + bs->bfpButtons |= BUTTON_ENABLEFLIGHT; + bs->bfpLastFlightDecision = bs->bfpFlightDecision; + } +} + +/* +================== +BotBFPCheckKiBoost +================== +*/ +static void BotBFPCheckKiBoost( bot_state_t *bs ) { + int maxKi = bs->cur_ps.stats[STAT_MAX_KI]; + int curKi = bs->cur_ps.stats[STAT_KI]; + float skill = bs->settings.skill; + + if ( maxKi <= 0 || curKi <= 0 ) { + return; + } + + if ( bs->bfpKiRecharging ) { + return; + } + + if ( curKi < maxKi * BFP_BOT_KI_BOOST_MIN_PCT ) { + return; + } + + if ( bs->cur_ps.stats[STAT_HITSTUN_TIME] > 0 ) { + return; + } + + if ( bs->cur_ps.weaponstate == WEAPON_BEAMSTRUGGLE ) { + if ( random() < ( 0.3f + 0.06f * skill ) ) { + bs->bfpButtons |= BUTTON_KI_USE; + } + return; + } + + if ( random() < ( 0.7f + 0.06f * skill ) ) { + bs->bfpButtons |= BUTTON_KI_USE; + } +} + +/* +================== +BotBFPCheckWeaponSlot +================== +*/ +static void BotBFPCheckWeaponSlot( bot_state_t *bs ) { + int weaponBits; + int available[BFP_NUM_ATTACK_SLOTS]; + int numAvailable; + int i, w; + int pick; + + if ( bs->cur_ps.weaponstate == WEAPON_RAISING + || bs->cur_ps.weaponstate == WEAPON_DROPPING ) { + return; + } + + if ( bs->weaponnum <= WP_NONE ) { + bs->weaponnum = WP_GAUNTLET; + trap_EA_SelectWeapon( bs->client, bs->weaponnum ); + } + + if ( bs->cur_ps.weaponstate == WEAPON_FIRING + || bs->cur_ps.weaponstate == WEAPON_ACTIVE + || bs->cur_ps.weaponstate == WEAPON_BEAMSTRUGGLE ) { + return; + } + + weaponBits = bs->cur_ps.stats[STAT_WEAPONS]; + + numAvailable = 0; + for ( i = 0; i < (int)BFP_NUM_ATTACK_SLOTS; i++ ) { + w = bfpAttackSlots[i]; + if ( weaponBits & ( 1 << w ) ) { + available[numAvailable++] = w; + } + } + + if ( numAvailable <= 1 ) { + if ( numAvailable == 1 && bs->weaponnum != available[0] ) { + bs->weaponnum = available[0]; + trap_EA_SelectWeapon( bs->client, bs->weaponnum ); + } + return; + } + + if ( bs->weaponchange_time > FloatTime() - BFP_BOT_WEAPON_SWITCH_MINTIME ) { + return; + } + if ( bs->weaponchange_time > FloatTime() - BFP_BOT_WEAPON_SWITCH_MAXTIME + && random() > 0.65f ) { + return; + } + + pick = available[0]; + for ( i = 0; i < numAvailable; i++ ) { + if ( available[i] == bs->weaponnum ) { + pick = available[ ( i + 1 ) % numAvailable ]; + break; + } + } + + if ( pick != bs->weaponnum ) { + bs->weaponnum = pick; + bs->weaponchange_time = FloatTime(); + trap_EA_SelectWeapon( bs->client, bs->weaponnum ); + } +} + +/* +================== +BotBFPCheckChargedAttack +================== +*/ +static void BotBFPCheckChargedAttack( bot_state_t *bs, aas_entityinfo_t *entinfo ) { + const bfpWeaponProfile_t *profile; + qboolean lineOfFireClear; + bsp_trace_t trace; + + bs->bfpForceAttackOff = qfalse; + + profile = BotBFPGetWeaponProfile( bs->weaponnum ); + if ( !profile ) { + return; + } + + if ( profile->chargeAutoFire ) { + if ( bs->cur_ps.weaponstate == WEAPON_FIRING || bs->cur_ps.weaponstate == WEAPON_ACTIVE ) { + if ( bs->bfpChargeAutoFireStartTime == 0 ) { + bs->bfpChargeAutoFireStartTime = FloatTime(); + } + if ( FloatTime() - bs->bfpChargeAutoFireStartTime < 4.0f ) { + bs->bfpButtons |= BUTTON_ATTACK; + } else { + bs->bfpButtons &= ~BUTTON_ATTACK; + bs->bfpForceAttackOff = qtrue; + bs->bfpChargeAutoFireStartTime = 0; + } + } else { + bs->bfpChargeAutoFireStartTime = 0; + } + } + + if ( bs->cur_ps.weaponstate == WEAPON_FIRING && profile->chargeAttack ) { + if ( bs->bfpButtons & BUTTON_KI_CHARGE ) { + return; + } + + bs->bfpButtons |= BUTTON_ATTACK; + + if ( bs->cur_ps.eFlags & EF_READY_KI_ATTACK ) { + BotAI_Trace( &trace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, + CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + lineOfFireClear = ( trace.fraction >= 1.0f || trace.ent == bs->enemy ); + + if ( lineOfFireClear ) { + bs->bfpForceAttackOff = qtrue; + } + } + return; + } + + if ( profile->attackType == ATK_SBEAM ) { + if ( bs->cur_ps.weaponstate == WEAPON_READY || bs->cur_ps.weaponstate == WEAPON_ACTIVE ) { + bs->bfpButtons |= BUTTON_ATTACK; + bs->bfpForceAttackOff = qfalse; + } + if ( bs->cur_ps.weaponstate == WEAPON_READY && bs->bfpLastWeaponState == WEAPON_ACTIVE ) { + bs->bfpForceAttackOff = qfalse; + bs->bfpButtons |= BUTTON_ATTACK; + } + bs->bfpLastWeaponState = bs->cur_ps.weaponstate; + } + + if ( bs->cur_ps.weaponstate == WEAPON_ACTIVE && profile->attackType == ATK_BEAM ) { + bs->bfpForceAttackOff = qtrue; + return; + } + + if ( bs->cur_ps.weaponstate == WEAPON_BEAMSTRUGGLE && profile->attackType == ATK_BEAM ) { + bs->bfpForceAttackOff = qtrue; + return; + } +} + +/* +================== +BotBFPCheckChase +================== +*/ +static void BotBFPCheckChase( bot_state_t *bs, aas_entityinfo_t *entinfo, qboolean forceMelee ) { + vec3_t dir, forward, up = { 0, 0, 1 }, sideward; + float dist; + qboolean visible; + float meleeStrafeRange = g_meleeDiveRange.value + 130; + + // evasion + if ( bs->bfpEvadeTime > FloatTime() ) { + if ( VectorLength( bs->bfpEvadeDir ) > 0.1f ) { + trap_BotMoveInDirection( bs->ms, bs->bfpEvadeDir, 400, MOVE_WALK ); + } + return; + } + + VectorSubtract( entinfo->origin, bs->origin, dir ); + dist = VectorLength( dir ); + + visible = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, BFP_BOT_FIELD_OF_VIEW, bs->enemy ); + + if ( visible && dist <= meleeStrafeRange ) { + bs->bfpButtons |= BUTTON_MELEE; + } + + if ( forceMelee ) { + if ( visible ) { + bs->attackchase_time = FloatTime() + 9999.0f; + dir[2] = 0; + if ( VectorNormalize( dir ) > 0.1f ) { + trap_BotMoveInDirection( bs->ms, dir, 400, MOVE_WALK ); + } + } + } + + if ( !forceMelee && visible && dist > BFP_BOT_CHASE_TRIGGER_RANGE ) { + bs->attackchase_time = FloatTime() + BFP_BOT_CHASE_DURATION; + return; + } + + if ( visible && dist < meleeStrafeRange && bs->attackchase_time <= FloatTime() ) { + VectorCopy( dir, forward ); + VectorNormalize( forward ); + CrossProduct( forward, up, sideward ); + if ( bs->flags & BFL_STRAFERIGHT ) { + VectorNegate( sideward, sideward ); + } + trap_BotMoveInDirection( bs->ms, sideward, 400, MOVE_WALK ); + if ( bs->bfpStrafeFlip_time < FloatTime() ) { + bs->flags ^= BFL_STRAFERIGHT; + bs->bfpStrafeFlip_time = FloatTime() + 0.5f + random() * 0.4f; + } + } +} + +/* +================== +BotBFPCheckZanzoken +================== +*/ +static void BotBFPCheckZanzoken( bot_state_t *bs, aas_entityinfo_t *entinfo ) { + float dist; + vec3_t dir; + qboolean wantsZanzoken; + + if ( bs->bfpZanzokenPhase != 0 ) { + bs->bfpRightmoveOverrideActive = qtrue; + switch ( bs->bfpZanzokenPhase ) { + case 1: + bs->bfpRightmoveOverride = ( bs->bfpZanzokenDir > 0 ) ? 127 : -127; + if ( FloatTime() >= bs->bfpZanzokenPhaseEnd_time ) { + bs->bfpZanzokenPhase = 2; + bs->bfpZanzokenPhaseEnd_time = FloatTime() + BFP_BOT_ZANZOKEN_RELEASE_MS * 0.001; + } + break; + case 2: + bs->bfpRightmoveOverride = 0; + if ( FloatTime() >= bs->bfpZanzokenPhaseEnd_time ) { + bs->bfpZanzokenPhase = 3; + bs->bfpZanzokenPhaseEnd_time = FloatTime() + BFP_BOT_ZANZOKEN_PRESS_MS * 0.001; + } + break; + case 3: + bs->bfpRightmoveOverride = ( bs->bfpZanzokenDir > 0 ) ? 127 : -127; + if ( FloatTime() >= bs->bfpZanzokenPhaseEnd_time ) { + bs->bfpZanzokenPhase = 0; + bs->bfpRightmoveOverrideActive = qfalse; + } + break; + } + return; + } + + wantsZanzoken = qfalse; + + if ( bs->cur_ps.stats[STAT_HITSTUN_TIME] > 0 + && bs->cur_ps.stats[STAT_HITSTUN_TIME] <= 3000 ) { + if ( bs->bfpZanzokenHitstunRetry_time <= FloatTime() ) { + bs->bfpZanzokenHitstunRetry_time = FloatTime() + BFP_BOT_ZANZOKEN_HITSTUN_RETRY_MS * 0.001; + if ( random() < BFP_BOT_ZANZOKEN_HITSTUN_CHANCE ) { + wantsZanzoken = qtrue; + } + } + } + + if ( bs->cur_ps.stats[STAT_HITSTUN_TIME] <= 0 ) { + bs->bfpZanzokenHitstunRetry_time = 0; + } + + if ( !wantsZanzoken ) { + if ( bs->cur_ps.weaponstate == WEAPON_ACTIVE + || bs->cur_ps.weaponstate == WEAPON_BEAMSTRUGGLE + || bs->cur_ps.weaponstate == WEAPON_STUN ) { + return; + } + if ( bs->cur_ps.pm_flags & ( PMF_KI_CHARGE | PMF_ULTIMATE_TIER ) ) { + return; + } + if ( bs->cur_ps.stats[STAT_KI] <= ( bs->cur_ps.stats[STAT_MAX_KI] * 0.05f ) ) { + return; + } + + if ( entinfo != NULL ) { + VectorSubtract( entinfo->origin, bs->origin, dir ); + dist = VectorLength( dir ); + if ( dist < BFP_BOT_ZANZOKEN_DANGER_RANGE + && BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, BFP_BOT_FIELD_OF_VIEW, bs->enemy ) ) { + if ( random() < 0.09f ) { + wantsZanzoken = qtrue; + } + } + } + } + + if ( wantsZanzoken ) { + bs->bfpZanzokenDir = ( random() < 0.5f ) ? -1 : 1; + bs->bfpZanzokenPhase = 1; + bs->bfpZanzokenPhaseEnd_time = FloatTime() + BFP_BOT_ZANZOKEN_PRESS_MS * 0.001; + bs->bfpRightmoveOverrideActive = qtrue; + bs->bfpRightmoveOverride = ( bs->bfpZanzokenDir > 0 ) ? 127 : -127; + } +} + +/* +================== +BotBFPBeginFrame +================== +*/ +void BotBFPBeginFrame( bot_state_t *bs ) { + bs->bfpButtons = 0; + bs->bfpForceAttackOff = qfalse; +} + +/* +================== +BotBFPCheckSixthSense +================== +*/ +static void BotBFPCheckSixthSense( bot_state_t *bs ) { + int i; + vec3_t dir, angles; + float squaredist; + aas_entityinfo_t entinfo; + + for ( i = 0; i < level.maxclients; i++ ) { + if ( i == bs->entitynum ) { + continue; + } + if ( BotSameTeam( bs, i ) ) { + continue; + } + + BotEntityInfo( i, &entinfo ); + if ( !entinfo.valid ) { + continue; + } + if ( EntityIsDead( &entinfo ) ) { + continue; + } + + VectorSubtract( entinfo.origin, bs->origin, dir ); + squaredist = VectorLengthSquared( dir ); + if ( squaredist > Square( BFP_BOT_SIXTHSENSE_RANGE ) ) { + continue; + } + + vectoangles( dir, angles ); + if ( InFieldOfVision( bs->viewangles, BFP_BOT_FIELD_OF_VIEW, angles ) ) { + continue; + } + + VectorCopy( angles, bs->ideal_viewangles ); + bs->flags |= BFL_IDEALVIEWSET; + + if ( bs->bfpSixthSenseStep_time < FloatTime() ) { + vec3_t away; + VectorNormalize( dir ); + VectorNegate( dir, away ); + away[2] = 0; + if ( VectorNormalize( away ) > 0.1f ) { + trap_BotMoveInDirection( bs->ms, away, 300, MOVE_WALK ); + } + bs->bfpSixthSenseStep_time = FloatTime() + 0.4f; + } + + return; + } +} + +/* +================== +BotBFPUniversalCheck +================== +*/ +void BotBFPUniversalCheck( bot_state_t *bs ) { + if ( bs->enemy < 0 ) { + if ( gametype == GT_CTF ) { + bot_goal_t goal; + if ( trap_BotGetTopGoal( bs->gs, &goal ) ) { + BotBFPCheckFlight( bs, NULL, &goal ); + BotBFPCheckKiBoost( bs ); + } + } + BotBFPCheckZanzoken( bs, NULL ); + BotBFPCheckKiRecharge( bs, NULL ); + BotBFPCompensateKiCharge( bs ); + BotBFPCheckSixthSense( bs ); + } +} + +/* +================== +BotBFPCombatAI +================== +*/ +void BotBFPCombatAI( bot_state_t *bs ) { + aas_entityinfo_t entinfo; + int currentHealth = bs->inventory[INVENTORY_HEALTH]; + + if ( bs->enemy < 0 ) { + bs->bfpKiRecharging = qfalse; + bs->bfpFlightDecision = qfalse; + return; + } + + BotEntityInfo( bs->enemy, &entinfo ); + + BotBFPCheckMelee( bs, &entinfo ); + BotBFPCheckKiRecharge( bs, &entinfo ); + BotBFPCompensateKiCharge( bs ); + BotBFPCheckFlight( bs, &entinfo, NULL ); + BotBFPCheckKiBoost( bs ); + + // use evasion after receiving damage + if ( bs->bfpLastHealth < 0 ) { + bs->bfpLastHealth = currentHealth; + } + if ( currentHealth < bs->bfpLastHealth && bs->bfpEvadeTime < FloatTime() ) { + vec3_t right, up = { 0, 0, 1 }; + vec3_t dirToEnemy; + VectorSubtract( entinfo.origin, bs->origin, dirToEnemy ); + VectorNormalize( dirToEnemy ); + CrossProduct( dirToEnemy, up, right ); + VectorNormalize( right ); + if ( random() < 0.5f ) { + VectorScale( right, ( random() < 0.5f ) ? 1 : -1, bs->bfpEvadeDir ); + } else { + VectorScale( dirToEnemy, -1, bs->bfpEvadeDir ); + } + bs->bfpEvadeDir[2] += ( random() - 0.5f ) * 0.2f; + VectorNormalize( bs->bfpEvadeDir ); + bs->bfpEvadeTime = FloatTime() + 0.5f + random() * 0.3f; + } + bs->bfpLastHealth = currentHealth; + + // don't use weapons on melee only + if ( g_meleeOnly.integer <= 0 ) { + BotBFPCheckWeaponSlot( bs ); + BotBFPCheckChargedAttack( bs, &entinfo ); + } + + BotBFPCheckChase( bs, &entinfo, ( g_meleeOnly.integer > 0 ) ); + BotBFPCheckZanzoken( bs, &entinfo ); +} diff --git a/source/game/ai_bfp.h b/source/game/ai_bfp.h new file mode 100644 index 0000000..40d5634 --- /dev/null +++ b/source/game/ai_bfp.h @@ -0,0 +1,13 @@ +/* +=========================================================================== + +BFP BOT AI + +=========================================================================== +*/ + +void BotBFPCombatAI( bot_state_t *bs ); +void BotBFPBeginFrame( bot_state_t *bs ); +void BotBFPApplyButtons( bot_state_t *bs, usercmd_t *ucmd ); +void BotBFPUniversalCheck( bot_state_t *bs ); +void BotBFPResetState( bot_state_t *bs ); diff --git a/source/game/ai_chat.c b/source/game/ai_chat.c index 9618af4..8c26f18 100644 --- a/source/game/ai_chat.c +++ b/source/game/ai_chat.c @@ -63,16 +63,12 @@ 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // @@ -89,17 +85,13 @@ 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // @@ -117,17 +109,13 @@ 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // @@ -146,18 +134,14 @@ 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // @@ -167,10 +151,11 @@ char *BotFirstClientInRankings(void) { bestclient = i; } } - EasyClientName(bestclient, name, 32); + EasyClientName( bestclient, name, sizeof( name ) ); return name; } + /* ================== BotLastClientInRankings @@ -180,18 +165,14 @@ 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // @@ -201,10 +182,11 @@ char *BotLastClientInRankings(void) { bestclient = i; } } - EasyClientName(bestclient, name, 32); + EasyClientName( bestclient, name, sizeof( name ) ); return name; } + /* ================== BotRandomOpponentName @@ -214,20 +196,16 @@ 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; //skip team mates @@ -248,21 +226,14 @@ char *BotRandomOpponentName(bot_state_t *bs) { 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'; - +static const char *BotMapTitle( void ) +{ return mapname; } @@ -272,7 +243,6 @@ char *BotMapTitle(void) { BotWeaponNameForMeansOfDeath ================== */ - char *BotWeaponNameForMeansOfDeath(int mod) { switch(mod) { case MOD_SHOTGUN: return "Shotgun"; @@ -289,6 +259,7 @@ char *BotWeaponNameForMeansOfDeath(int mod) { case MOD_BFG: case MOD_BFG_SPLASH: return "BFG10K"; case MOD_GRAPPLE: return "Grapple"; + case MOD_KI_ATTACK: return "Ki Blast"; default: return "[unknown weapon]"; } } @@ -359,6 +330,7 @@ int BotValidChatPosition(bot_state_t *bs) { if (BotIsDead(bs)) return qtrue; //never start chatting with a powerup if (bs->inventory[INVENTORY_QUAD] || + bs->inventory[INVENTORY_ENVIRONMENTSUIT] || bs->inventory[INVENTORY_HASTE] || bs->inventory[INVENTORY_INVISIBILITY] || bs->inventory[INVENTORY_REGEN] || @@ -469,7 +441,9 @@ int BotChat_StartLevel(bot_state_t *bs) { return qfalse; } // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; @@ -504,7 +478,9 @@ int BotChat_EndLevel(bot_state_t *bs) { return qtrue; } // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; @@ -556,7 +532,9 @@ int BotChat_Death(bot_state_t *bs) { if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; //if fast chatting is off if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; @@ -594,16 +572,26 @@ int BotChat_Death(bot_state_t *bs) { bs->botdeathtype == MOD_SUICIDE || bs->botdeathtype == MOD_TARGET_LASER || bs->botdeathtype == MOD_TRIGGER_HURT || + // BFP - Ki attack method of death + bs->botdeathtype == MOD_KI_ATTACK || 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 { + // BFP - That part is replaced to "death_suicide" because in the BFP player_t.c botfiles mention the following notes: + // note to anyone who may read this: there shouldn't be any gauntlets + // note to anyone who may read this: there shouldn't be any rails + // note to anyone who may read this: there shouldn't be any BFGs if ((bs->botdeathtype == MOD_GAUNTLET || bs->botdeathtype == MOD_RAILGUN || bs->botdeathtype == MOD_BFG || - bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { + bs->botdeathtype == MOD_BFG_SPLASH || + // BFP - Ki attack method of death + bs->botdeathtype == MOD_KI_ATTACK) && random() < 0.5) { + BotAI_BotInitialChat(bs, "death_suicide", name, NULL); +#if 0 if (bs->botdeathtype == MOD_GAUNTLET) BotAI_BotInitialChat(bs, "death_gauntlet", name, // 0 @@ -619,6 +607,7 @@ int BotChat_Death(bot_state_t *bs) { name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); +#endif } //choose between insult and praise else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { @@ -653,7 +642,9 @@ int BotChat_Kill(bot_state_t *bs) { if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; @@ -713,11 +704,13 @@ int BotChat_EnemySuicide(bot_state_t *bs) { if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; if (BotNumActivePlayers() <= 1) return qfalse; // - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENEMYSUICIDE, 0, 1); //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; @@ -757,7 +750,9 @@ int BotChat_HitTalking(bot_state_t *bs) { //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd * 0.5) return qfalse; @@ -765,7 +760,7 @@ int BotChat_HitTalking(bot_state_t *bs) { 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); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); // BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); bs->lastchat_time = FloatTime(); @@ -797,7 +792,9 @@ int BotChat_HitNoDeath(bot_state_t *bs) { //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd * 0.5) return qfalse; @@ -835,7 +832,9 @@ int BotChat_HitNoKill(bot_state_t *bs) { //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd * 0.5) return qfalse; @@ -869,7 +868,9 @@ int BotChat_Random(bot_state_t *bs) { if (BotIsObserver(bs)) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; + if ( gametype == GT_TOURNAMENT + || gametype == GT_SURVIVAL ) // BFP - Survival + return qfalse; //don't chat when doing something important :) if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || @@ -929,9 +930,9 @@ BotChatTime ================== */ float BotChatTime(bot_state_t *bs) { - int cpm; + //int cpm; - cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); + //cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; } diff --git a/source/game/ai_cmd.c b/source/game/ai_cmd.c index 37cd9d8..ba8a9ac 100644 --- a/source/game/ai_cmd.c +++ b/source/game/ai_cmd.c @@ -217,54 +217,57 @@ float BotGetTime(bot_match_t *match) { return 0; } + /* ================== FindClientByName ================== */ -int FindClientByName(char *name) { +int FindClientByName( const char *name ) { + char buf[ MAX_INFO_STRING ]; 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 < level.maxclients; i++ ) { + ClientName( i, buf, sizeof( buf ) ); + if ( !Q_stricmp( buf, name ) ) + return i; // exact match } - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - ClientName(i, buf, sizeof(buf)); - if (stristr(buf, name)) return i; + + for ( i = 0; i < level.maxclients ; i++ ) { + ClientName(i, buf, sizeof( buf ) ); + if ( stristr( buf, name ) ) + return i; // partial match } + return -1; } + /* ================== FindEnemyByName ================== */ -int FindEnemyByName(bot_state_t *bs, char *name) { - int i; +int FindEnemyByName( bot_state_t *bs, const char *name ) { char buf[MAX_INFO_STRING]; - static int maxclients; + int i; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; 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++) { + + for (i = 0; i < level.maxclients; i++) { if (BotSameTeam(bs, i)) continue; ClientName(i, buf, sizeof(buf)); if (stristr(buf, name)) return i; } + return -1; } + /* ================== NumPlayersOnSameTeam @@ -273,15 +276,11 @@ 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++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); - if (strlen(buf)) { + if (buf[0]) { if (BotSameTeam(bs, i+1)) num++; } } @@ -1038,8 +1037,7 @@ void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { //get the sub team name trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); //set the sub team name - strncpy(bs->subteam, teammate, 32); - bs->subteam[31] = '\0'; + Q_strncpyz( bs->subteam, teammate, sizeof( bs->subteam ) ); // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); @@ -1060,7 +1058,7 @@ void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // - if (strlen(bs->subteam)) + if (bs->subteam[0] != '\0') { BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); @@ -1080,7 +1078,7 @@ void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // - if (strlen(bs->subteam)) { + if (bs->subteam[0] != '\0') { BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); } else { @@ -1109,7 +1107,7 @@ void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); //BotGPSToPosition(buf, position); - sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); + Q_sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); position[2] += 0.5; areanum = BotPointAreaNum(position); if (!areanum) { @@ -1210,8 +1208,8 @@ void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { trap_EA_Command(bs->client, "kill"); // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); // + client = ClientFromName(netname); BotVoiceChat(bs, client, VOICECHAT_TAUNT); // trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); } @@ -1230,8 +1228,7 @@ void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { 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'; + Q_strncpyz( bs->teamleader, teammate, sizeof( bs->teamleader ) ); } //chats for someone else else { diff --git a/source/game/ai_dmnet.c b/source/game/ai_dmnet.c index f89b902..423894a 100644 --- a/source/game/ai_dmnet.c +++ b/source/game/ai_dmnet.c @@ -47,6 +47,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_team.h" +#include "ai_bfp.h" //data file headers #include "chars.h" //characteristics #include "inv.h" //indexes into the inventory @@ -85,7 +86,7 @@ void BotDumpNodeSwitches(bot_state_t *bs) { ClientName(bs->client, netname, sizeof(netname)); BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); for (i = 0; i < numnodeswitches; i++) { - BotAI_Print(PRT_MESSAGE, nodeswitch[i]); + BotAI_Print( PRT_MESSAGE, "%s", nodeswitch[i] ); } BotAI_Print(PRT_FATAL, ""); } @@ -102,7 +103,7 @@ void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); #ifdef DEBUG if (0) { - BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); + BotAI_Print( PRT_MESSAGE, "%s", nodeswitch[numnodeswitches] ); } #endif //DEBUG numnodeswitches++; @@ -193,7 +194,7 @@ int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { //check if the bot should go for air if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; - //if the bot is carrying the enemy flag + // if the bot is carrying a flag or cubes if (BotCTFCarryingFlag(bs)) { //if the bot is just a few secs away from the base if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, @@ -213,7 +214,7 @@ int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { trap_BotGoalName(goal.number, buf, sizeof(buf)); BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); } - */ + */ return ret; } @@ -300,7 +301,7 @@ int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { trap_BotGetTopGoal(bs->gs, goal); trap_BotGoalName(goal->number, buf, sizeof(buf)); BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); - */ + */ bs->ltg_time = FloatTime() + 20; } else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though @@ -389,7 +390,6 @@ int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - // trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); bs->teammessage_time = 0; } //if accompanying the companion for 3 minutes @@ -569,11 +569,10 @@ int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) bs->teammessage_time = 0; } // - if (bs->lastkilledplayer == bs->teamgoal.entitynum) { + if (bs->killedenemy_time > bs->teamgoal_time - TEAM_KILL_SOMEONE && bs->lastkilledplayer == bs->teamgoal.entitynum) { EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "kill_done", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->lastkilledplayer = -1; bs->ltgtype = 0; } // @@ -591,7 +590,6 @@ int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - // trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); bs->teammessage_time = 0; } //set the bot goal @@ -684,9 +682,7 @@ int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) bs->ltgtype = 0; } // - if (bs->camp_range > 0) { - //FIXME: move around a bit - } + //FIXME: move around a bit // trap_BotResetAvoidReach(bs->ms); return qfalse; @@ -1103,6 +1099,8 @@ int BotSelectActivateWeapon(bot_state_t *bs) { return WEAPONINDEX_PLASMAGUN; else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) return WEAPONINDEX_LIGHTNING; + else if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && bs->inventory[INVENTORY_GRENADES] > 0) + return WEAPONINDEX_GRENADE_LAUNCHER; else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) return WEAPONINDEX_RAILGUN; else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) @@ -1114,14 +1112,17 @@ int BotSelectActivateWeapon(bot_state_t *bs) { } } + +// BFP - No Team Arena proximity mines bot detection logic /* ================== BotClearPath - try to deactivate obstacles like proximity mines on the bot's path +try to deactivate obstacles like proximity mines on the bot's path ================== */ void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { +#if 0 int i, bestmine; float dist, bestdist; vec3_t target, dir; @@ -1220,6 +1221,7 @@ void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { } } } +#endif } /* @@ -1325,7 +1327,7 @@ int AINode_Seek_ActivateEntity(bot_state_t *bs) { } else { // if the bot has no goal - if (!goal) { + if (!goal) { // FIXME: always false? bs->activatestack->time = 0; } // if the bot does not have a shoot goal @@ -1751,11 +1753,12 @@ void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle fight", "", s); trap_BotResetLastAvoidReach(bs->ms); bs->ainode = AINode_Battle_Fight; + bs->flags &= ~BFL_FIGHTSUICIDAL; } /* ================== -AIEnter_Battle_Fight +AIEnter_Battle_SuicidalFight ================== */ void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { @@ -1798,7 +1801,9 @@ int AINode_Battle_Fight(bot_state_t *bs) { #endif } //if no enemy - if (bs->enemy < 0) { + if (bs->enemy < 0 || ( g_gametype.integer >= GT_TEAM && bs->enemy < level.maxclients && + level.clients[bs->client].sess.sessionTeam == level.clients[bs->enemy].sess.sessionTeam ) ) { + bs->enemy = -1; AIEnter_Seek_LTG(bs, "battle fight: no enemy"); return qfalse; } @@ -1811,6 +1816,10 @@ int AINode_Battle_Fight(bot_state_t *bs) { if (bs->enemysuicide) { BotChat_EnemySuicide(bs); } + // BFP - Gesture after killing opponent + if ( bs->lastkilledplayer == bs->enemy ) { + trap_EA_Gesture( bs->client ); + } if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "battle fight: enemy dead"); @@ -1885,8 +1894,7 @@ int AINode_Battle_Fight(bot_state_t *bs) { if (BotCanAndWantsToRocketJump(bs)) { bs->tfl |= TFL_ROCKETJUMP; } - //choose the best weapon to fight with - BotChooseWeapon(bs); + // BFP - before Q3 line here: BotChooseWeapon(bs); //do attack movements moveresult = BotAttackMove(bs, bs->tfl); //if the movement failed @@ -1902,6 +1910,8 @@ int AINode_Battle_Fight(bot_state_t *bs) { BotAimAtEnemy(bs); //attack the enemy if possible BotCheckAttack(bs); + // BFP - BFP bot combat + BotBFPCombatAI( bs ); //if the bot wants to retreat if (!(bs->flags & BFL_FIGHTSUICIDAL)) { if (BotWantsToRetreat(bs)) { @@ -2192,8 +2202,7 @@ int AINode_Battle_Retreat(bot_state_t *bs) { } // BotAIBlocked(bs, &moveresult, qfalse); - //choose the best weapon to fight with - BotChooseWeapon(bs); + // BFP - before Q3 line here: BotChooseWeapon(bs); //if the view is fixed for the movement if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); @@ -2201,7 +2210,7 @@ int AINode_Battle_Retreat(bot_state_t *bs) { 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 the bot is skilled enough if (attack_skill > 0.3) { BotAimAtEnemy(bs); } @@ -2220,6 +2229,8 @@ int AINode_Battle_Retreat(bot_state_t *bs) { if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; //attack the enemy if possible BotCheckAttack(bs); + // BFP - BFP bot combat + BotBFPCombatAI( bs ); // return qtrue; } @@ -2331,8 +2342,7 @@ int AINode_Battle_NBG(bot_state_t *bs) { BotAIBlocked(bs, &moveresult, qfalse); //update the attack inventory values BotUpdateBattleInventory(bs, bs->enemy); - //choose the best weapon to fight with - BotChooseWeapon(bs); + // BFP - before Q3 line here: BotChooseWeapon(bs); //if the view is fixed for the movement if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); @@ -2340,7 +2350,7 @@ int AINode_Battle_NBG(bot_state_t *bs) { 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 the bot is skilled enough and the enemy is visible if (attack_skill > 0.3) { //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) BotAimAtEnemy(bs); @@ -2360,6 +2370,8 @@ int AINode_Battle_NBG(bot_state_t *bs) { if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; //attack the enemy if possible BotCheckAttack(bs); + // BFP - BFP bot combat + BotBFPCombatAI( bs ); // return qtrue; } diff --git a/source/game/ai_dmq3.c b/source/game/ai_dmq3.c index 6e1eada..c5f048a 100644 --- a/source/game/ai_dmq3.c +++ b/source/game/ai_dmq3.c @@ -72,7 +72,7 @@ bot_waypoint_t *botai_freewaypoints; //NOTE: not using a cvars which can be updated because the game should be reloaded anyway int gametype; //game type -int maxclients; //maximum number of clients +//int maxclients; //maximum number of clients vmCvar_t bot_grapple; vmCvar_t bot_rocketjump; @@ -107,15 +107,16 @@ int blue_numaltroutegoals; BotSetUserInfo ================== */ -void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { +static void BotSetUserInfo( bot_state_t *bs, const char *key, const char *value ) { char userinfo[MAX_INFO_STRING]; - trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); - Info_SetValueForKey(userinfo, key, value); - trap_SetUserinfo(bs->client, userinfo); + trap_GetUserinfo( bs->client, userinfo, sizeof( userinfo ) ); + Info_SetValueForKey( userinfo, key, value ); + trap_SetUserinfo( bs->client, userinfo ); ClientUserinfoChanged( bs->client ); } + /* ================== BotCTFCarryingFlag @@ -229,9 +230,12 @@ qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { if (EntityCarriesFlag(entinfo)) { return qfalse; } + // BFP - No invis powerup +#if 0 if (entinfo->powerups & (1 << PW_INVIS)) { return qtrue; } +#endif return qfalse; } @@ -591,7 +595,7 @@ void BotCTFSeekGoals(bot_state_t *bs) { if (bs->ctfroam_time > FloatTime()) return; //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) + if (BotAggression(bs) < 0.5) return; //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); @@ -708,91 +712,96 @@ int BotPointAreaNum(vec3_t origin) { return 0; } + /* ================== ClientName ================== */ -char *ClientName(int client, char *name, int size) { - char buf[MAX_INFO_STRING]; +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]"; + if ( (unsigned) client >= MAX_CLIENTS ) { + BotAI_Print( PRT_ERROR, "ClientName: client out of range\n" ); + Q_strncpyz( name, "[client out of range]", size ); + return name; } - trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); - strncpy(name, Info_ValueForKey(buf, "n"), size-1); - name[size-1] = '\0'; + + trap_GetConfigstring( CS_PLAYERS + client, buf, sizeof( buf ) ); + Q_strncpyz( name, Info_ValueForKey( buf, "n" ), size ); Q_CleanStr( name ); + return name; } + /* ================== ClientSkin ================== */ -char *ClientSkin(int client, char *skin, int size) { - char buf[MAX_INFO_STRING]; +char *ClientSkin( int client, char *skin, int size ) { + char buf[ MAX_INFO_STRING ]; - if (client < 0 || client >= MAX_CLIENTS) { + if ( (unsigned) 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'; + + trap_GetConfigstring( CS_PLAYERS + client, buf, sizeof( buf ) ); + Q_strncpyz( skin, Info_ValueForKey( buf, "model" ), size ); + return skin; } + /* ================== ClientFromName ================== */ -int ClientFromName(char *name) { +int ClientFromName( const char *name ) { int i; - char buf[MAX_INFO_STRING]; - static int maxclients; + char buf[ MAX_INFO_STRING ]; - 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)); + for ( i = 0; i < level.maxclients; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); Q_CleanStr( buf ); - if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + if ( !Q_stricmp( Info_ValueForKey( buf, "n" ), name ) ) + return i; } return -1; } + /* ================== ClientOnSameTeamFromName ================== */ -int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { - int i; +int ClientOnSameTeamFromName( bot_state_t *bs, const char *name ) { char buf[MAX_INFO_STRING]; - static int maxclients; + int i; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (!BotSameTeam(bs, i)) + for ( i = 0; i < level.maxclients; i++ ) { + if ( !BotSameTeam( bs, i ) ) continue; - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); Q_CleanStr( buf ); - if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + if ( !Q_stricmp( Info_ValueForKey( buf, "n" ), name ) ) + return i; } + return -1; } + /* ================== stristr ================== */ -char *stristr(char *str, char *charset) { +const char *stristr(const char *str, const char *charset) { int i; while(*str) { @@ -805,6 +814,7 @@ char *stristr(char *str, char *charset) { return NULL; } + /* ================== EasyClientName @@ -815,23 +825,23 @@ char *EasyClientName(int client, char *buf, int size) { char *str1, *str2, *ptr, c; char name[128]; - strcpy(name, ClientName(client, name, sizeof(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, (int)strlen(ptr+1)+1); + for (ptr = strchr(name, ' '); ptr; ptr = strchr(name, ' ')) { + memmove(ptr, ptr+1, strlen(ptr+1)+1); } //check for [x] and ]x[ clan names - str1 = strstr(name, "["); - str2 = strstr(name, "]"); + str1 = strchr(name, '['); + str2 = strchr(name, ']'); if (str1 && str2) { - if (str2 > str1) memmove(str1, str2+1, (int)strlen(str2+1)+1); - else memmove(str2, str1+1, (int)strlen(str1+1)+1); + 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, (int)strlen(name+2)+1); + memmove(name, name+2, strlen(name+2)+1); } //only allow lower case alphabet characters ptr = name; @@ -846,11 +856,12 @@ char *EasyClientName(int client, char *buf, int size) { ptr++; } else { - memmove(ptr, ptr+1, (int)strlen(ptr + 1)+1); + memmove(ptr, ptr+1, strlen(ptr + 1)+1); } } - strncpy(buf, name, size-1); - buf[size-1] = '\0'; + + Q_strncpyz( buf, name, size ); + return buf; } @@ -916,9 +927,12 @@ void BotSetupForMovement(bot_state_t *bs) { initmove.or_moveflags |= MFL_TELEPORTED; } //set the waterjump flag + // BFP - No handling PMF_TIME_WATERJUMP +#if 0 if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { initmove.or_moveflags |= MFL_WATERJUMP; } +#endif //set presence type if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; else initmove.presencetype = PRESENCE_NORMAL; @@ -950,35 +964,42 @@ void BotUpdateInventory(bot_state_t *bs) { //armor bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; //weapons + // BFP - TODO: Just a test, bfp_weapon.cfg needs to be implemented and replace all stuff here bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; - bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; + bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; - bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; - bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; - bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; - bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; - bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; + + bs->inventory[INVENTORY_ROCKETLAUNCHER] = + bs->inventory[INVENTORY_LIGHTNING] = + bs->inventory[INVENTORY_RAILGUN] = + bs->inventory[INVENTORY_PLASMAGUN] = + bs->inventory[INVENTORY_BFG10K] = 0; //ammo - bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; - bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; - bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; - bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; - bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; - bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; - bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; - bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; + bs->inventory[INVENTORY_SHELLS] = + bs->inventory[INVENTORY_BULLETS] = + bs->inventory[INVENTORY_GRENADES] = + bs->inventory[INVENTORY_CELLS] = + bs->inventory[INVENTORY_LIGHTNINGAMMO] = + bs->inventory[INVENTORY_ROCKETS] = + bs->inventory[INVENTORY_SLUGS] = + bs->inventory[INVENTORY_BFGAMMO] = 1; //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; + // BFP - No battlesuit powerup + // bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; + // BFP - No haste powerup + // bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + // BFP - No invis powerup + // bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + // BFP - No regen powerup + // bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; + // BFP - No flight powerup + // 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; @@ -1166,46 +1187,8 @@ BotAggression ================== */ float BotAggression(bot_state_t *bs) { - //if the bot has quad - if (bs->inventory[INVENTORY_QUAD]) { - //if the bot is not holding the gauntlet or the enemy is really nearby - if (bs->weaponnum != WP_GAUNTLET || - bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { - return 70; - } - } - //if the enemy is located way higher than the bot - if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; - //if the bot is very low on health - if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; - //if the bot is low on health - if (bs->inventory[INVENTORY_HEALTH] < 80) { - //if the bot has insufficient armor - if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; - } - //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_LIGHTNING] > 0 && - bs->inventory[INVENTORY_LIGHTNINGAMMO] > 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 plasmagun - if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && - bs->inventory[INVENTORY_CELLS] > 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; + // BFP - Read CHARACTERISTIC_AGGRESSION from botfiles + return trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AGGRESSION, 0, 1 ); } /* @@ -1253,7 +1236,7 @@ int BotWantsToRetreat(bot_state_t *bs) { if (bs->ltgtype == LTG_GETFLAG) return qtrue; // - if (BotAggression(bs) < 50) + if (BotAggression(bs) < 0.5) return qtrue; return qfalse; } @@ -1279,7 +1262,7 @@ int BotWantsToChase(bot_state_t *bs) { if (bs->ltgtype == LTG_GETFLAG) return qfalse; // - if (BotAggression(bs) > 50) + if (BotAggression(bs) > 0.5) return qtrue; return qfalse; } @@ -1346,15 +1329,6 @@ int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { //if the bot can use the rocketlauncher if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_NAILGUN] > 0 && - bs->inventory[INVENTORY_NAILS] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && - bs->inventory[INVENTORY_MINES] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_CHAINGUN] > 0 && - bs->inventory[INVENTORY_BELT] > 40) return qtrue; //if the bot can use the plasmagun if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 20) return qtrue; @@ -1419,9 +1393,9 @@ int BotWantsToCamp(bot_state_t *bs) { return qfalse; } //if the bot isn't healthy anough - if (BotAggression(bs) < 50) return qfalse; + if (BotAggression(bs) < 0.5) 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]) && + 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; @@ -1677,21 +1651,20 @@ BotSameTeam ================== */ int BotSameTeam(bot_state_t *bs, int entnum) { - char info1[1024], info2[1024]; - if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + extern gclient_t g_clients[ MAX_CLIENTS ]; + + if ( (unsigned) bs->client >= MAX_CLIENTS ) { //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); return qfalse; } - if (entnum < 0 || entnum >= MAX_CLIENTS) { + if ( (unsigned) entnum >= MAX_CLIENTS ) { //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); return qfalse; } if ( gametype >= GT_TEAM ) { - trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); - trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); - // - if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; + if ( g_clients[bs->client].sess.sessionTeam == g_clients[entnum].sess.sessionTeam ) + return qtrue; } return qfalse; } @@ -1742,6 +1715,8 @@ float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int //calculate middle of bounding box BotEntityInfo(ent, &entinfo); + if (!entinfo.valid) + return 0; VectorAdd(entinfo.mins, entinfo.maxs, middle); VectorScale(middle, 0.5, middle); VectorAdd(entinfo.origin, middle, middle); @@ -1839,7 +1814,7 @@ BotFindEnemy */ int BotFindEnemy(bot_state_t *bs, int curenemy) { int i, healthdecrease; - float f, alertness, easyfragger, vis; + float alertness, easyfragger, vis; float squaredist, cursquaredist; aas_entityinfo_t entinfo, curenemyinfo; vec3_t dir, angles; @@ -1861,11 +1836,13 @@ int BotFindEnemy(bot_state_t *bs, int curenemy) { cursquaredist = 0; } // - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; //if it's the current enemy if (i == curenemy) continue; + //if the enemy has targeting disabled + if (g_entities[i].flags & FL_NOTARGET) continue; // BotEntityInfo(i, &entinfo); // @@ -1893,16 +1870,12 @@ int BotFindEnemy(bot_state_t *bs, int curenemy) { if (curenemy >= 0 && squaredist > cursquaredist) continue; } //end if //if the bot has no - if (squaredist > Square(900.0 + alertness * 4000.0)) continue; + if (squaredist > Square(1200.0 + alertness * 9000.0)) continue; //if on the same team if (BotSameTeam(bs, i)) continue; - //if the bot's health decreased or the enemy is shooting - if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) - f = 360; - else - f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); + // BFP - No fov adjustment, just 360, very visible, even behind //check if the enemy is visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); if (vis <= 0) continue; //if the enemy is quite far away, not shooting and the bot is not damaged if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) @@ -1940,7 +1913,7 @@ int BotTeamFlagCarrierVisible(bot_state_t *bs) { float vis; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -1973,7 +1946,7 @@ int BotTeamFlagCarrier(bot_state_t *bs) { int i; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -2003,7 +1976,7 @@ int BotEnemyFlagCarrierVisible(bot_state_t *bs) { float vis; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -2042,7 +2015,7 @@ void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies *teammates = 0; if (enemies) *enemies = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -2088,6 +2061,14 @@ void BotAimAtEnemy(bot_state_t *bs) { bot_goal_t goal; bsp_trace_t trace; vec3_t target; + gentity_t *botent = &g_entities[bs->entitynum]; + + // BFP - If bot is blinded, severely reduce aim accuracy + if ( botent->blindedTime && level.time - botent->blindedTime < 6000 ) { + // make the bot aim wildly + bs->ideal_viewangles[YAW] += 60 * crandom(); + return; + } //if the bot has no enemy if (bs->enemy < 0) { @@ -2266,10 +2247,10 @@ void BotAimAtEnemy(bot_state_t *bs) { //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 the hitpoint is near enough the ground target if (VectorLengthSquared(dir) < Square(60)) { VectorSubtract(trace.endpos, start, dir); - //if the hitpoint is far anough from the bot + //if the hitpoint is far enough from the bot if (VectorLengthSquared(dir) > Square(100)) { //check if the bot is visible from the ground target trace.endpos[2] += 1; @@ -2291,7 +2272,7 @@ void BotAimAtEnemy(bot_state_t *bs) { // VectorCopy(bs->lastenemyorigin, bestorigin); bestorigin[2] += 8; - //if the bot is skilled anough + //if the bot is skilled enough if (aim_skill > 0.5) { //do prediction shots around corners if (wi.number == WP_BFG || @@ -2374,7 +2355,21 @@ void BotCheckAttack(bot_state_t *bs) { weaponinfo_t wi; bsp_trace_t trace; aas_entityinfo_t entinfo; + weapon_t weapon; vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + gentity_t *botent; + + botent = &g_entities[bs->entitynum]; + + // BFP - If bot is blinded, don't attack + if ( botent->blindedTime && level.time - botent->blindedTime < 6000 ) { + return; // Bot can't see to attack + } + + // BFP - Avoid the bot tries to attack with this status, otherwise keeps stunned while pressing attack key + if ( bs->cur_ps.stats[STAT_HITSTUN_TIME] > 0 ) { + return; + } attackentity = bs->enemy; // @@ -2437,7 +2432,7 @@ void BotCheckAttack(bot_state_t *bs) { VectorMA(start, -12, forward, start); BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); //if the entity is a client - if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { + if (trace.ent >= 0 && trace.ent < MAX_CLIENTS) { if (trace.ent != attackentity) { //if a teammate is hit if (BotSameTeam(bs, trace.ent)) @@ -2457,6 +2452,12 @@ void BotCheckAttack(bot_state_t *bs) { //FIXME: check if a teammate gets radial damage } } + + weapon = bs->cur_ps.weapon; + if ( weapon >= WP_MACHINEGUN && weapon <= WP_BFG && !bs->cur_ps.ammo[ weapon ] ) { + return; + } + //if fire has to be release to activate weapon if (wi.flags & WFL_FIRERELEASED) { if (bs->flags & BFL_ATTACKED) { @@ -2475,18 +2476,11 @@ 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}; @@ -2502,7 +2496,7 @@ void BotMapScripts(bot_state_t *bs) { } shootbutton = qfalse; //if an enemy is below this bounding box then shoot the button - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -2769,7 +2763,8 @@ BotFuncDoorGoal int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { int modelindex, entitynum; char model[MAX_INFO_STRING]; - vec3_t mins, maxs, origin, angles; + vec3_t mins, maxs, origin; + //vec3_t angles; //shoot at the shootable door trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); @@ -2778,7 +2773,7 @@ int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *act modelindex = atoi(model+1); if (!modelindex) return qfalse; - VectorClear(angles); + //VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); //door origin VectorAdd(mins, maxs, origin); @@ -2804,7 +2799,8 @@ BotTriggerMultipleGoal int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { int i, areas[10], numareas, modelindex, entitynum; char model[128]; - vec3_t start, end, mins, maxs, angles; + vec3_t start, end, mins, maxs; + //vec3_t angles; vec3_t origin, goalorigin; activategoal->shoot = qfalse; @@ -2816,7 +2812,7 @@ int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal modelindex = atoi(model+1); if (!modelindex) return qfalse; - VectorClear(angles); + //VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); //trigger origin VectorAdd(mins, maxs, origin); @@ -2950,8 +2946,8 @@ int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { ================== BotGetActivateGoal - returns the number of the bsp entity to activate - goal->entitynum will be set to the game entity to activate +returns the number of the bsp entity to activate +goal->entitynum will be set to the game entity to activate ================== */ //#define OBSTACLEDEBUG @@ -2964,7 +2960,8 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ char targetname[10][128]; aas_entityinfo_t entinfo; aas_areainfo_t areainfo; - vec3_t origin, angles, absmins, absmaxs; + vec3_t origin, absmins, absmaxs; + //vec3_t angles; memset(activategoal, 0, sizeof(bot_activategoal_t)); BotEntityInfo(entitynum, &entinfo); @@ -2978,7 +2975,7 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ return 0; } trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); - if (!classname) { + if (!*classname) { BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); return 0; } @@ -3008,7 +3005,7 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ if (*model) { modelindex = atoi(model+1); if (modelindex) { - VectorClear(angles); + //VectorClear(angles); BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); // numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); @@ -3080,9 +3077,9 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ continue; // if the bot tries to activate this button already if ( bs->activatestack && bs->activatestack->inuse && - bs->activatestack->goal.entitynum == activategoal->goal.entitynum && - bs->activatestack->time > FloatTime() && - bs->activatestack->start_time < FloatTime() - 2) + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) continue; // if the bot is in a reachability area if ( trap_AAS_AreaReachability(bs->areanum) ) { @@ -3105,9 +3102,9 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ continue; // if the bot tries to activate this trigger already if ( bs->activatestack && bs->activatestack->inuse && - bs->activatestack->goal.entitynum == activategoal->goal.entitynum && - bs->activatestack->time > FloatTime() && - bs->activatestack->start_time < FloatTime() - 2) + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) continue; // if the bot is in a reachability area if ( trap_AAS_AreaReachability(bs->areanum) ) { @@ -3231,7 +3228,7 @@ open, which buttons to activate etc. */ void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { int movetype, bspent; - vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; + vec3_t hordir, start, /*end, mins, maxs,*/ sideward, angles, up = {0, 0, 1}; aas_entityinfo_t entinfo; bot_activategoal_t activategoal; @@ -3252,7 +3249,7 @@ void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { #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 +#endif // if blocked by a bsp model and the bot wants to activate it if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { // find the bsp entity which should be activated in order to get the blocking entity out of the way @@ -3295,9 +3292,9 @@ void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { // 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); + //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; @@ -3306,7 +3303,7 @@ void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { // if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); // try to crouch straight forward? - if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { + if (!trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { // perform the movement if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { // flip the avoid direction flag @@ -3575,7 +3572,7 @@ void BotCheckEvents(bot_state_t *bs, entityState_t *state) { } case EV_GLOBAL_SOUND: { - if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + if (state->eventParm < 0 || state->eventParm >= MAX_SOUNDS) { BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); break; } @@ -3603,11 +3600,19 @@ void BotCheckEvents(bot_state_t *bs, entityState_t *state) { if (gametype == GT_CTF) { switch(state->eventParm) { case GTS_RED_CAPTURE: + // BFP - Gesture after capturing the flag + if ( BotTeam( bs ) == TEAM_RED && bs->flagcarrier == bs->client ) { + trap_EA_Gesture( bs->client ); + } bs->blueflagstatus = 0; bs->redflagstatus = 0; bs->flagstatuschanged = qtrue; break; //see BotMatch_CTF case GTS_BLUE_CAPTURE: + // BFP - Gesture after capturing the flag + if ( BotTeam( bs ) == TEAM_BLUE && bs->flagcarrier == bs->client ) { + trap_EA_Gesture( bs->client ); + } bs->blueflagstatus = 0; bs->redflagstatus = 0; bs->flagstatuschanged = qtrue; @@ -3646,7 +3651,7 @@ void BotCheckEvents(bot_state_t *bs, entityState_t *state) { { //if this sound is played on the bot if (state->number == bs->client) { - if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + if (state->eventParm < 0 || state->eventParm >= MAX_SOUNDS) { BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); break; } @@ -3704,6 +3709,7 @@ void BotCheckEvents(bot_state_t *bs, entityState_t *state) { case EV_USE_ITEM12: case EV_USE_ITEM13: case EV_USE_ITEM14: + case EV_USE_ITEM15: break; } } @@ -3850,7 +3856,8 @@ void BotDeathmatchAI(bot_state_t *bs, float thinktime) { Info_SetValueForKey(userinfo, "sex", gender); trap_SetUserinfo(bs->client, userinfo); //set the team - if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { + if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT + && g_gametype.integer != GT_SURVIVAL ) { // BFP - Survival Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); trap_EA_Command(bs->client, buf); } @@ -4019,8 +4026,7 @@ void BotSetupDeathmatchAI(void) { int ent, modelnum; char model[128]; - gametype = trap_Cvar_VariableIntegerValue("g_gametype"); - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + gametype = trap_Cvar_VariableIntegerValue( "g_gametype" ); trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); diff --git a/source/game/ai_dmq3.h b/source/game/ai_dmq3.h index cf01067..f68e06d 100644 --- a/source/game/ai_dmq3.h +++ b/source/game/ai_dmq3.h @@ -62,13 +62,11 @@ qboolean EntityIsDead(aas_entityinfo_t *entinfo); qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); //returns true if the entity is shooting qboolean EntityIsShooting(aas_entityinfo_t *entinfo); -// set a user info key/value pair -void BotSetUserInfo(bot_state_t *bs, char *key, char *value); // set the team status (offense, defense etc.) void BotSetTeamStatus(bot_state_t *bs); //returns the name of the client char *ClientName(int client, char *name, int size); -//returns an simplyfied client name +//returns a simplified 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); @@ -151,10 +149,10 @@ 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); +const char *stristr(const char *str, const char *charset); //returns the number of the client with the given name -int ClientFromName(char *name); -int ClientOnSameTeamFromName(bot_state_t *bs, char *name); +int ClientFromName( const char *name ); +int ClientOnSameTeamFromName( bot_state_t *bs, const char *name ); // int BotPointAreaNum(vec3_t origin); // @@ -169,7 +167,7 @@ void BotMapScripts(bot_state_t *bs); #define CTF_SKIN_BLUETEAM "blue" extern int gametype; //game type -extern int maxclients; //maximum number of clients +extern char mapname[ MAX_QPATH ]; extern vmCvar_t bot_grapple; extern vmCvar_t bot_rocketjump; diff --git a/source/game/ai_main.c b/source/game/ai_main.c index e5f6a47..bd2b0f0 100644 --- a/source/game/ai_main.c +++ b/source/game/ai_main.c @@ -49,6 +49,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_vcmd.h" +#include "ai_bfp.h" // #include "chars.h" @@ -57,7 +58,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MAX_PATH 144 - //bot states bot_state_t *botstates[MAX_CLIENTS]; //number of bots @@ -83,6 +83,7 @@ vmCvar_t bot_interbreedbots; vmCvar_t bot_interbreedcycle; vmCvar_t bot_interbreedwrite; +char mapname[MAX_QPATH]; void ExitLevel( void ); @@ -92,12 +93,12 @@ void ExitLevel( void ); BotAI_Print ================== */ -void QDECL BotAI_Print(int type, char *fmt, ...) { +void QDECL BotAI_Print( int type, const char *fmt, ... ) { char str[2048]; va_list ap; va_start(ap, fmt); - vsprintf(str, fmt, ap); + Q_vsprintf(str, fmt, ap); va_end(ap); switch(type) { @@ -147,11 +148,12 @@ void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, 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->surface.value = 0; + bsptrace->surface.flags = trace.surfaceFlags; bsptrace->ent = trace.entityNum; bsptrace->exp_dist = 0; bsptrace->sidenum = 0; - bsptrace->contents = 0; + bsptrace->contents = trace.contents; } /* @@ -174,23 +176,32 @@ int BotAI_GetClientState( int clientNum, playerState_t *state ) { return qtrue; } + /* ================== BotAI_GetEntityState ================== */ -int BotAI_GetEntityState( int entityNum, entityState_t *state ) { - gentity_t *ent; +qboolean BotAI_GetEntityState( int entityNum, entityState_t *state ) { + const gentity_t *ent; + + ent = g_entities + entityNum; + + if ( !ent->inuse || !ent->r.linked ) { + memset( state, 0, sizeof( entityState_t ) ); + return qfalse; + } + + if ( ent->r.svFlags & SVF_NOCLIENT ) { + memset( state, 0, sizeof( entityState_t ) ); + return qfalse; + } - 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 @@ -210,6 +221,7 @@ int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) return sequence + 1; } + /* ================== BotAI_BotInitialChat @@ -253,7 +265,7 @@ void BotTestAAS(vec3_t origin) { if (bot_testsolid.integer) { if (!trap_AAS_Initialized()) return; areanum = BotPointAreaNum(origin); - if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); + if (areanum) BotAI_Print(PRT_MESSAGE, "\rempty area"); else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); } else if (bot_testclusters.integer) { @@ -375,26 +387,26 @@ void BotTeamplayReport(void) { char buf[MAX_INFO_STRING]; BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { BotReportStatus(botstates[i]); } } BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { BotReportStatus(botstates[i]); @@ -500,11 +512,11 @@ void BotSetInfoConfigString(bot_state_t *bs) { break; } } - cs = va("l\\%s\\c\\%s\\a\\%s", + cs = va("l\\%s\\c\\%s\\a\\%s", leader, carrying, action); - trap_SetConfigstring (CS_BOTINFO + bs->client, cs); + trap_SetConfigstring (CS_BOTINFO + bs->client, cs); } /* @@ -516,14 +528,14 @@ void BotUpdateInfoConfigStrings(void) { int i; char buf[MAX_INFO_STRING]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; BotSetInfoConfigString(botstates[i]); } @@ -607,7 +619,7 @@ void BotInterbreedEndMatch(void) { bot_interbreedmatchcount = 0; // trap_Cvar_Update(&bot_interbreedwrite); - if (strlen(bot_interbreedwrite.string)) { + if (bot_interbreedwrite.string[0] != '\0') { BotWriteInterbreeded(bot_interbreedwrite.string); trap_Cvar_Set("bot_interbreedwrite", ""); } @@ -631,8 +643,15 @@ void BotInterbreeding(void) { ExitLevel(); return; } + + if ( gametype != GT_SURVIVAL ) { // BFP - Survival + trap_Cvar_Set("g_gametype", va("%d", GT_SURVIVAL)); + ExitLevel(); + return; + } + //shutdown all the bots - for (i = 0; i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (botstates[i] && botstates[i]->inuse) { BotAIShutdownClient(botstates[i]->client, qfalse); } @@ -751,7 +770,7 @@ void BotChangeViewAngles(bot_state_t *bs, float thinktime) { // if (bot_challenge.integer) { //smooth slowdown view model - diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[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], @@ -792,6 +811,7 @@ void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3] vec3_t angles, forward, right; short temp; int j; + float f, r, u, m; //clear the whole structure memset(ucmd, 0, sizeof(usercmd_t)); @@ -846,22 +866,39 @@ void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3] 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; + f = DotProduct( forward, bi->dir ); + r = DotProduct( right, bi->dir ); + u = fabs( forward[2] ) * bi->dir[2]; + m = fabs( f ); + + if ( fabs( r ) > m ) { + m = fabs( r ); + } + + if ( fabs( u ) > m) { + m = fabs( u ); + } + + if ( m > 0 ) { + f *= bi->speed / m; + r *= bi->speed / m; + u *= bi->speed / m; + } + + ucmd->forwardmove = f; + ucmd->rightmove = r; + ucmd->upmove = u; + + 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; + 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); + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove = -127; } /* @@ -963,19 +1000,19 @@ int BotAI(int client, float thinktime) { { /*ConfigStringModified*/ } else if (!Q_stricmp(buf, "print")) { //remove first and last quote from the chat message - memmove(args, args+1, (int)strlen(args)); + memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); } else if (!Q_stricmp(buf, "chat")) { //remove first and last quote from the chat message - memmove(args, args+1, (int)strlen(args)); + memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); } else if (!Q_stricmp(buf, "tchat")) { //remove first and last quote from the chat message - memmove(args, args+1, (int)strlen(args)); + memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); } @@ -999,9 +1036,15 @@ int BotAI(int client, float thinktime) { bs->eye[2] += bs->cur_ps.viewheight; //get the area the bot is in bs->areanum = BotPointAreaNum(bs->origin); + // BFP - Reset per-frame button state + BotBFPBeginFrame( bs ); //the real AI BotDeathmatchAI(bs, thinktime); //set the weapon selection every AI frame + // BFP - Avoid "weapon number out of range" engine print error + if ( bs->weaponnum <= WP_NONE ) { + bs->weaponnum = WP_GAUNTLET; + } trap_EA_SelectWeapon(bs->client, bs->weaponnum); //subtract the delta angles for (j = 0; j < 3; j++) { @@ -1081,7 +1124,7 @@ void BotReadSessionData(bot_state_t *bs) { var = va( "botsession%i", bs->client ); trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); - sscanf(s, + Q_sscanf(s, "%i %i %i %i %i %i %i %i" " %f %f %f" " %f %f %f" @@ -1111,13 +1154,16 @@ void BotReadSessionData(bot_state_t *bs) { BotAISetupClient ============== */ -int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { +qboolean BotAISetupClient( int client, struct bot_settings_s *settings, qboolean restart ) { char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; bot_state_t *bs; int errnum; if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); bs = botstates[client]; + if ( bs == NULL ) { + return qfalse; + } if (bs && bs->inuse) { BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); @@ -1225,7 +1271,7 @@ int BotAIShutdownClient(int client, qboolean restart) { } trap_BotFreeMoveState(bs->ms); - //free the goal state` + //free the goal state trap_BotFreeGoalState(bs->gs); //free the chat file trap_BotFreeChatState(bs->cs); @@ -1248,6 +1294,7 @@ int BotAIShutdownClient(int client, qboolean restart) { return qtrue; } + /* ============== BotResetState @@ -1281,6 +1328,8 @@ void BotResetState(bot_state_t *bs) { BotFreeWaypoints(bs->patrolpoints); //reset the whole state memset(bs, 0, sizeof(bot_state_t)); + // BFP - Reset BFP bot state + BotBFPResetState( bs ); //copy back some state stuff that should not be reset bs->ms = movestate; bs->gs = goalstate; @@ -1307,16 +1356,18 @@ BotAILoadMap ============== */ int BotAILoadMap( int restart ) { + char serverinfo[MAX_INFO_STRING]; int i; - vmCvar_t mapname; - if (!restart) { - trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); - trap_BotLibLoadMap( mapname.string ); + trap_GetServerinfo( serverinfo, sizeof( serverinfo ) ); + Q_strncpyz( mapname, Info_ValueForKey( serverinfo, "mapname" ), sizeof( mapname ) ); + + if ( !restart ) { + trap_BotLibLoadMap( mapname ); } - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { + for ( i = 0; i < level.maxclients; i++ ) { + if ( botstates[i] && botstates[i]->inuse ) { BotResetState( botstates[i] ); botstates[i]->setupcount = 4; } @@ -1340,6 +1391,7 @@ int BotAIStartFrame(int time) { static int local_time; static int botlib_residual; static int lastbotthink_time; + static qboolean skip[MAX_GENTITIES], *s; G_CheckBotSpawn(); @@ -1362,7 +1414,7 @@ int BotAIStartFrame(int time) { if (bot_pause.integer) { // execute bot user commands every frame - for( i = 0; i < MAX_CLIENTS; i++ ) { + for( i = 0; i < level.maxclients; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } @@ -1416,18 +1468,31 @@ int BotAIStartFrame(int time) { if (!trap_AAS_Initialized()) return qfalse; //update entities in the botlib - for (i = 0; i < MAX_GENTITIES; i++) { + s = skip; + ent = g_entities; + for ( i = 0; i < level.num_entities; i++, s++, ent++ ) { ent = &g_entities[i]; - if (!ent->inuse) { - trap_BotLibUpdateEntity(i, NULL); + if ( !ent->inuse || !ent->r.linked || ent->r.svFlags & SVF_NOCLIENT ) { + if ( *s == qfalse ) { + *s = qtrue; + trap_BotLibUpdateEntity( i, NULL ); + } continue; } - if (!ent->r.linked) { - trap_BotLibUpdateEntity(i, NULL); + // do not update missiles + if ( ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK ) { + if ( *s == qfalse ) { + *s = qtrue; + trap_BotLibUpdateEntity( i, NULL ); + } continue; } - if (ent->r.svFlags & SVF_NOCLIENT) { - trap_BotLibUpdateEntity(i, NULL); + // do not update event only entities + if ( ent->s.eType > ET_EVENTS ) { + if ( *s == qfalse ) { + *s = qtrue; + trap_BotLibUpdateEntity( i, NULL ); + } continue; } // do not update missiles @@ -1459,7 +1524,7 @@ int BotAIStartFrame(int time) { state.groundent = ent->s.groundEntityNum; state.modelindex = ent->s.modelindex; state.modelindex2 = ent->s.modelindex2; - state.frame = ent->s.frame; + // state.frame = ent->s.frame; state.event = ent->s.event; state.eventParm = ent->s.eventParm; state.powerups = ent->s.powerups; @@ -1467,7 +1532,8 @@ int BotAIStartFrame(int time) { state.torsoAnim = ent->s.torsoAnim; state.weapon = ent->s.weapon; // - trap_BotLibUpdateEntity(i, &state); + *s = qfalse; + trap_BotLibUpdateEntity( i, &state ); } BotAIRegularUpdate(); @@ -1476,7 +1542,7 @@ int BotAIStartFrame(int time) { floattime = trap_AAS_Time(); // execute scheduled bot AI - for( i = 0; i < MAX_CLIENTS; i++ ) { + for( i = 0; i < level.maxclients; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } @@ -1496,7 +1562,7 @@ int BotAIStartFrame(int time) { // execute bot user commands every frame - for( i = 0; i < MAX_CLIENTS; i++ ) { + for( i = 0; i < level.maxclients; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } @@ -1505,6 +1571,10 @@ int BotAIStartFrame(int time) { } BotUpdateInput(botstates[i], time, elapsed_time); + // BFP - BFP Battle nodes + BotBFPUniversalCheck( botstates[i] ); + // BFP - BFP buttons + BotBFPApplyButtons( botstates[i], &botstates[i]->lastucmd ); trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); } @@ -1516,65 +1586,95 @@ int BotAIStartFrame(int time) { BotInitLibrary ============== */ -int BotInitLibrary(void) { - char buf[144]; +int BotInitLibrary( void ) { + char buf[MAX_CVAR_VALUE_STRING]; //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); + trap_Cvar_VariableStringBuffer( "sv_maxclients", buf, sizeof( buf ) ); + if ( !buf[0] ) + 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); + if ( buf[0] ) + 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); + if ( buf[0] ) + 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); + if ( buf[0] ) + trap_BotLibVarSet( "max_levelitems", buf ); + //game type trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "0"); + if ( !buf[0] ) + strcpy( buf, "0" ); trap_BotLibVarSet("g_gametype", buf); //bot developer mode and log file trap_BotLibVarSet("bot_developer", bot_developer.string); trap_BotLibVarSet("log", buf); //no chatting trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); + if ( buf[0] ) + trap_BotLibVarSet( "nochat", "0" ); + //visualize jump pads trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); + if ( buf[0] ) + trap_BotLibVarSet( "bot_visualizejumppads", buf ); + //forced clustering calculations trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); + if ( buf[0] ) + trap_BotLibVarSet( "forceclustering", buf ); + //forced reachability calculations trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); + if ( buf[0] ) + trap_BotLibVarSet( "forcereachability", buf ); + //force writing of AAS to file trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); + if ( buf[0] ) + trap_BotLibVarSet( "forcewrite", buf ); + //no AAS optimization trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); + if ( buf[0] ) + trap_BotLibVarSet( "aasoptimize", buf ); + // trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf); + if ( buf[0] ) + trap_BotLibVarSet( "saveroutingcache", buf ); + //reload instead of cache bot character files trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "0"); + if ( !buf[0] ) + 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); + if ( buf[0] ) + trap_BotLibVarSet( "basedir", buf ); + + //home directory + trap_Cvar_VariableStringBuffer("fs_homepath", buf, sizeof(buf)); + if ( buf[0] ) + trap_BotLibVarSet( "homedir", 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); + trap_Cvar_VariableStringBuffer( "fs_game", buf, sizeof( buf ) ); + if ( buf[0] ) + trap_BotLibVarSet( "gamedir", buf ); + //setup the bot library return trap_BotLibSetup(); } @@ -1609,7 +1709,9 @@ int BotAISetup( int restart ) { memset( botstates, 0, sizeof(botstates) ); errnum = BotInitLibrary(); - if (errnum != BLERR_NOERROR) return qfalse; + if ( errnum != BLERR_NOERROR ) + return qfalse; + return qtrue; } @@ -1625,8 +1727,8 @@ int BotAIShutdown( int restart ) { //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) { + for ( i = 0; i < level.maxclients; i++ ) { + if ( botstates[i] && botstates[i]->inuse ) { BotAIShutdownClient(botstates[i]->client, restart); } } diff --git a/source/game/ai_main.h b/source/game/ai_main.h index 5d0e9ec..f2ff169 100644 --- a/source/game/ai_main.h +++ b/source/game/ai_main.h @@ -128,7 +128,7 @@ typedef struct bot_state_s 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 + int entityeventTime[MAX_GENTITIES]; //last entity event time // bot_settings_t settings; //several bot settings int (*ainode)(struct bot_state_s *bs); //current AI node @@ -249,7 +249,7 @@ typedef struct bot_state_s 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 + char teamleader[MAX_NETNAME]; //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 @@ -257,7 +257,6 @@ typedef struct bot_state_s int numteammates; //number of team mates int redflagstatus; //0 = at base, 1 = not at base int blueflagstatus; //0 = at base, 1 = not at base - int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag int flagstatuschanged; //flag status changed int forceorders; //true if forced to give orders int flagcarrier; //team mate carrying the enemy flag @@ -277,6 +276,36 @@ typedef struct bot_state_s bot_waypoint_t *patrolpoints; //patrol points bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for int patrolflags; //patrol flags + + // BFP - BFP combat AI (ai_bfp.c) + int bfpButtons; // BFP button bits (BUTTON_ENABLEFLIGHT/KI_USE/KI_CHARGE/MELEE) requested this frame + qboolean bfpKiRecharging; // true while the bot is deliberately charging ki + float bfpKiRechargeInterrupted_time; // time the bot's ki charge was broken by taking damage + int bfpLastHealth; // health value the BFP AI last saw, -1 until initialized + + float bfpLastMelee_time; // time the bot last pressed BUTTON_MELEE, for a breather between swings + float bfpStrafeFlip_time; // next time the bot's forced close-range strafe direction may flip + + qboolean bfpFlightDecision; // cached on/off flight decision, held for a while instead of re-rolled every think frame + qboolean bfpLastFlightDecision; // previous flight decision, to detect toggles + float bfpFlightReroll_time; // next time the flight decision above is allowed to change + + int bfpZanzokenPhase; // 0=idle, 1=first tap held, 2=released waiting gap, 3=second tap held (see BotBFPCheckZanzoken) + float bfpZanzokenPhaseEnd_time; // when the current phase ends and the machine advances + int bfpZanzokenDir; // +1 or -1, which direction the in-progress zanzoken attempt is going + float bfpZanzokenHitstunRetry_time; // next time BotBFPCheckZanzoken() is allowed to re-roll its escape chance while hitstun is active, so it can retry a few times over the ~3s stun instead of only once + + int bfpRightmoveOverride; // value to force onto lastucmd.rightmove this frame while a zanzoken attempt is in progress + qboolean bfpRightmoveOverrideActive; // true while bfpRightmoveOverride should be applied (0 is a valid override value, so this can't be inferred from the int alone) + + qboolean bfpForceAttackOff; // forces and stops attacking + float bfpKiCompensate_time; // last real time BotBFPCompensateKiCharge() topped up ki, so it can scale the gain by actual elapsed time + float bfpSixthSenseStep_time; // cooldown between BotBFPCheckSixthSense()'s instinctive step-back reaction + float bfpChargeAutoFireStartTime; // time when chargeAutoFire weapon started firing, used for 4s limit + int bfpLastWeaponState; // last weaponstate, used to detect transitions (e.g. SBEAM ending) + + float bfpEvadeTime; // evasion mode bot time + vec3_t bfpEvadeDir; // direction in which to move during the evasion } bot_state_t; //resets the whole bot state @@ -290,10 +319,10 @@ extern float floattime; #define FloatTime() floattime // from the game source -void QDECL BotAI_Print(int type, char *fmt, ...); +void QDECL BotAI_Print( int type, const 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 ); +qboolean BotAI_GetEntityState( int entityNum, entityState_t *state ); int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); int BotTeamLeader(bot_state_t *bs); diff --git a/source/game/ai_team.c b/source/game/ai_team.c index 56ebe84..a202b3c 100644 --- a/source/game/ai_team.c +++ b/source/game/ai_team.c @@ -83,16 +83,12 @@ 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // @@ -127,28 +123,25 @@ int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxtea int i, j, k, numteammates, traveltime; char buf[MAX_INFO_STRING]; - static int maxclients; int traveltimes[MAX_CLIENTS]; bot_goal_t *goal = NULL; - if (gametype == GT_CTF || gametype == GT_1FCTF) { + if (gametype == GT_CTF) { if (BotTeam(bs) == 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++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // - if (BotSameTeam(bs, i)) { + if (BotSameTeam(bs, i) && goal) { // traveltime = BotClientTravelTimeToGoal(i, goal); // @@ -301,6 +294,7 @@ void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { int teammates[MAX_CLIENTS]; char name[MAX_NETNAME], carriername[MAX_NETNAME]; + memset( teammates, 0, sizeof( teammates ) ); numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //different orders based on the number of team mates @@ -659,6 +653,7 @@ void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; + memset( teammates, 0, sizeof( teammates ) ); //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference @@ -839,23 +834,19 @@ void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { ================== BotTeamOrders - FIXME: defend key areas? +FIXME: defend key areas? ================== */ void BotTeamOrders(bot_state_t *bs) { int teammates[MAX_CLIENTS]; int numteammates, i; char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); numteammates = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; 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; + if (!buf[0] || !*Info_ValueForKey(buf, "n")) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // @@ -970,8 +961,7 @@ void BotTeamAI(bot_state_t *bs) { trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); ClientName(bs->client, netname, sizeof(netname)); - strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); - bs->teamleader[sizeof(bs->teamleader)] = '\0'; + Q_strncpyz( bs->teamleader, netname, sizeof( bs->teamleader ) ); bs->becometeamleader_time = 0; } return; diff --git a/source/game/ai_vcmd.c b/source/game/ai_vcmd.c index eae5f0d..c009f4c 100644 --- a/source/game/ai_vcmd.c +++ b/source/game/ai_vcmd.c @@ -61,7 +61,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA typedef struct voiceCommand_s { char *cmd; - void (*func)(bot_state_t *bs, int client, int mode); + void(*func)(bot_state_t *bs, int client, int mode); } voiceCommand_t; /* @@ -467,7 +467,7 @@ voiceCommand_t voiceCommands[] = { }; int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { - int i, voiceOnly, clientNum, color; + int i, clientNum; char *ptr, buf[MAX_MESSAGE_SIZE], *cmd; if (!TeamPlayIsOn()) { @@ -480,15 +480,14 @@ int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { Q_strncpyz(buf, voiceChat, sizeof(buf)); cmd = buf; - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + for (/*ptr = cmd*/; *cmd && *cmd > ' '; cmd++); while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - voiceOnly = atoi(ptr); + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); while (*cmd && *cmd <= ' ') *cmd++ = '\0'; clientNum = atoi(ptr); - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + for (/*ptr = cmd*/; *cmd && *cmd > ' '; cmd++); while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - color = atoi(ptr); if (!BotSameTeam(bs, clientNum)) { return qfalse; diff --git a/source/game/be_ai_move.h b/source/game/be_ai_move.h index fb80819..592d50f 100644 --- a/source/game/be_ai_move.h +++ b/source/game/be_ai_move.h @@ -42,7 +42,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #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 +// BFP - MFL_WATERJUMP is unused +// #define MFL_WATERJUMP 16 //bot is waterjumping #define MFL_TELEPORTED 32 //bot is being teleported #define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple #define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook diff --git a/source/game/bg_events.h b/source/game/bg_events.h new file mode 100644 index 0000000..2c79b91 --- /dev/null +++ b/source/game/bg_events.h @@ -0,0 +1,129 @@ +#ifdef EVENT_ENUMS + #define DECLARE_EVENT( ev ) ev +#endif + +#ifdef EVENT_STRINGS + #define DECLARE_EVENT( ev ) #ev +#endif + +// BFP - Highly modified to match original BFP demo networking events +DECLARE_EVENT( EV_NONE ), // 0 + +DECLARE_EVENT( EV_UNUSED_INDEX1 ), // 1 +DECLARE_EVENT( EV_UNUSED_INDEX2 ), // 2 +DECLARE_EVENT( EV_UNUSED_INDEX3 ), // 3 +DECLARE_EVENT( EV_UNUSED_INDEX4 ), // 4 +DECLARE_EVENT( EV_UNUSED_INDEX5 ), // 5 +DECLARE_EVENT( EV_UNUSED_INDEX6 ), // 6 +DECLARE_EVENT( EV_UNUSED_INDEX7 ), // 7 +DECLARE_EVENT( EV_UNUSED_INDEX8 ), // 8 +DECLARE_EVENT( EV_UNUSED_INDEX9 ), // 9 + +DECLARE_EVENT( EV_MELEE_READY ), // BFP - EV_MELEE_READY (10), original BFP: preparing melee +DECLARE_EVENT( EV_MELEE ), // BFP - EV_MELEE (11), melee attack +DECLARE_EVENT( EV_UNUSED_INDEX12 ), // 12 +DECLARE_EVENT( EV_TIER_RESET ), // EV_TIER_RESET (13), reset tier when the player respawns and changes to the default or a bit less ki energy? +// BFP - EV_TIER_0-4 (14-18), when the player frags, increases their PL and obtains a new skill (in the last tier, transforms) +DECLARE_EVENT( EV_TIER_0 ), // 14 +DECLARE_EVENT( EV_TIER_1 ), // 15 +DECLARE_EVENT( EV_TIER_2 ), // 16 +DECLARE_EVENT( EV_TIER_3 ), // 17 +DECLARE_EVENT( EV_TIER_4 ), // 18 + +DECLARE_EVENT( EV_ZANZOKEN_IN ), // BFP - EV_ZANZOKEN_IN (19), Short-Range Teleport (Zanzoken) +DECLARE_EVENT( EV_ZANZOKEN_OUT ), // BFP - EV_ZANZOKEN_OUT (20), stop/leaves zanzoken +DECLARE_EVENT( EV_KI_BOOST ), // BFP - EV_KI_BOOST (21), original BFP: enables aura and ki trail +DECLARE_EVENT( EV_ENABLE_FLIGHT ), // BFP - EV_ENABLE_FLIGHT (22), enable flight +DECLARE_EVENT( EV_FOOTSTEP ), // 23 +DECLARE_EVENT( EV_FOOTSTEP_METAL ), // 24 +DECLARE_EVENT( EV_FOOTSPLASH ), // 25 +DECLARE_EVENT( EV_FOOTWADE ), // 26 +DECLARE_EVENT( EV_SWIM ), // 27 +DECLARE_EVENT( EV_STEP_4 ), // 28 +DECLARE_EVENT( EV_STEP_8 ), // 29 +DECLARE_EVENT( EV_STEP_12 ), // 30 +DECLARE_EVENT( EV_STEP_16 ), // 31 + +DECLARE_EVENT( EV_FALL_SHORT ), // 32 +DECLARE_EVENT( EV_FALL_MEDIUM ), // 33 +DECLARE_EVENT( EV_FALL_FAR ), // 34 +DECLARE_EVENT( EV_JUMP_PAD ), // boing sound at origin, jump sound on player +DECLARE_EVENT( EV_JUMP ), // 36 +DECLARE_EVENT( EV_JUMP_2 ), // 37 + +DECLARE_EVENT( EV_WATER_TOUCH ), // foot touches +DECLARE_EVENT( EV_WATER_LEAVE ), // foot leaves +DECLARE_EVENT( EV_WATER_UNDER ), // head touches +DECLARE_EVENT( EV_WATER_CLEAR ), // head leaves +DECLARE_EVENT( EV_ITEM_PICKUP ), // normal item pickups are predictable +DECLARE_EVENT( EV_GLOBAL_ITEM_PICKUP ), // powerup / team sounds are broadcast to everyone +DECLARE_EVENT( EV_NOAMMO ), // 44 +DECLARE_EVENT( EV_CHANGE_WEAPON ), // 45 +DECLARE_EVENT( EV_FIRE_WEAPON ), // 46 + +DECLARE_EVENT( EV_USE_ITEM0 ), // 47 +DECLARE_EVENT( EV_USE_ITEM1 ), // 48 +DECLARE_EVENT( EV_USE_ITEM2 ), // 49 +DECLARE_EVENT( EV_USE_ITEM3 ), // 50 +DECLARE_EVENT( EV_USE_ITEM4 ), // 51 +DECLARE_EVENT( EV_USE_ITEM5 ), // 52 +DECLARE_EVENT( EV_USE_ITEM6 ), // 53 +DECLARE_EVENT( EV_USE_ITEM7 ), // 54 +DECLARE_EVENT( EV_USE_ITEM8 ), // 55 +DECLARE_EVENT( EV_USE_ITEM9 ), // 56 +DECLARE_EVENT( EV_USE_ITEM10 ), // 57 +DECLARE_EVENT( EV_USE_ITEM11 ), // 58 +DECLARE_EVENT( EV_USE_ITEM12 ), // 59 +DECLARE_EVENT( EV_USE_ITEM13 ), // 60 +DECLARE_EVENT( EV_USE_ITEM14 ), // 61 +DECLARE_EVENT( EV_USE_ITEM15 ), // 62 + +DECLARE_EVENT( EV_ITEM_RESPAWN ), // 63 +DECLARE_EVENT( EV_ITEM_POP ), // 64 +DECLARE_EVENT( EV_PLAYER_TELEPORT_IN ), // 65 +DECLARE_EVENT( EV_PLAYER_TELEPORT_OUT ), // 66 +DECLARE_EVENT( EV_GRENADE_BOUNCE ), // eventParm will be the soundindex + +DECLARE_EVENT( EV_GENERAL_SOUND ), // 68 +DECLARE_EVENT( EV_GLOBAL_SOUND ), // no attenuation +DECLARE_EVENT( EV_GLOBAL_TEAM_SOUND ), // 70 +DECLARE_EVENT( EV_BULLET_HIT_FLESH ), // 71 +DECLARE_EVENT( EV_BULLET_HIT_WALL ), // 72 +DECLARE_EVENT( EV_MISSILE_HIT ), // 73 +DECLARE_EVENT( EV_MISSILE_MISS ), // 74 +DECLARE_EVENT( EV_MISSILE_MISS_METAL ), // 75 +DECLARE_EVENT( EV_MISSILE_DETONATE ), // BFP - EV_MISSILE_DETONATE (76), in some moment, ki attack beam/projectile explodes +DECLARE_EVENT( EV_RAILTRAIL ), // 77 +DECLARE_EVENT( EV_SHOTGUN ), // otherEntity is the shooter +DECLARE_EVENT( EV_UNUSED_INDEX79 ), // 79 + +DECLARE_EVENT( EV_PAIN ), // 80 +DECLARE_EVENT( EV_DEATH1 ), // 81 +DECLARE_EVENT( EV_DEATH2 ), // 82 +DECLARE_EVENT( EV_DEATH3 ), // 83 +DECLARE_EVENT( EV_OBITUARY ), // 84 + +DECLARE_EVENT( EV_POWERUP_QUAD ), // 85 +DECLARE_EVENT( EV_POWERUP_BATTLESUIT ), // 86 +DECLARE_EVENT( EV_UNUSED_INDEX87 ), // 87 +DECLARE_EVENT( EV_GIB_PLAYER ), // gib a previously living player + +DECLARE_EVENT( EV_UNUSED_INDEX89 ), // 89 +DECLARE_EVENT( EV_UNUSED_INDEX90 ), // 90 +DECLARE_EVENT( EV_SCOREPLUM ), // score plum + +DECLARE_EVENT( EV_TAUNT ), // 92 +DECLARE_EVENT( EV_UNUSED_INDEX93 ), // 93 +DECLARE_EVENT( EV_UNUSED_INDEX94 ), // 94 +DECLARE_EVENT( EV_UNUSED_INDEX95 ), // 95 +DECLARE_EVENT( EV_BLINDING ), // BFP - EV_BLINDING (reusing 96), when ki attack explodes after some time +DECLARE_EVENT( EV_DEBUG_LINE ), // 97 +DECLARE_EVENT( EV_STOPLOOPINGSOUND ), // 98 + +DECLARE_EVENT( EV_SPARK ) // BFP - EV_SPARK (99), beam struggle + +#ifdef EVENT_ENUMS + , DECLARE_EVENT( EV_MAX ) +#endif + +#undef DECLARE_EVENT diff --git a/source/game/bg_lib.c b/source/game/bg_lib.c index 617dc90..3c916d2 100644 --- a/source/game/bg_lib.c +++ b/source/game/bg_lib.c @@ -1,4 +1,4 @@ -// +// Copyright (C) 1999-2000 Id Software, Inc. // // bg_lib,c -- standard C library replacement routines used by code // compiled for the virtual machine @@ -38,13 +38,6 @@ * SUCH DAMAGE. */ -#if defined(LIBC_SCCS) && !defined(lint) -#if 0 -static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; -#endif -static const char rcsid[] = -#endif /* LIBC_SCCS and not lint */ - // bk001127 - needed for DLL's #if !defined( Q3_VM ) typedef int cmp_t(const void *, const void *); @@ -54,7 +47,7 @@ static char* med3(char *, char *, char *, cmp_t *); static void swapfunc(char *, char *, int, int); #ifndef min -#define min(a, b) (a) < (b) ? a : b +#define min(a, b) ((a) < (b) ? a : b) #endif /* @@ -192,10 +185,9 @@ loop: SWAPINIT(a, es); // this file is excluded from release builds because of intrinsics -// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' #if defined ( Q3_VM ) -int strlen( const char *string ) { +size_t strlen( const char *string ) { const char *s; s = string; @@ -214,7 +206,8 @@ char *strcat( char *strDestination, const char *strSource ) { s++; } while ( *strSource ) { - *s++ = *strSource++; + *s = *strSource; + s++; strSource++; } *s = 0; return strDestination; @@ -225,7 +218,8 @@ char *strcpy( char *strDestination, const char *strSource ) { s = strDestination; while ( *strSource ) { - *s++ = *strSource++; + *s = *strSource; + s++; strSource++; } *s = 0; return strDestination; @@ -248,9 +242,13 @@ char *strchr( const char *string, int c ) { } string++; } - return (char *)0; + if(c) + return NULL; + else + return (char *) string; } + char *strstr( const char *string, const char *strCharSet ) { while ( *string ) { int i; @@ -267,12 +265,7 @@ char *strstr( const char *string, const char *strCharSet ) { } return (char *)0; } -#endif // bk001211 -// bk001120 - presumably needed for Mac -//#if !defined(_MSC_VER) && !defined(__linux__) -// bk001127 - undid undo -#if defined ( Q3_VM ) int tolower( int c ) { if ( c >= 'A' && c <= 'Z' ) { c += 'a' - 'A'; @@ -280,7 +273,6 @@ int tolower( int c ) { return c; } - int toupper( int c ) { if ( c >= 'a' && c <= 'z' ) { c += 'A' - 'a'; @@ -288,9 +280,6 @@ int toupper( int c ) { return c; } -#endif -//#ifndef _MSC_VER - void *memmove( void *dest, const void *src, size_t count ) { int i; @@ -305,7 +294,7 @@ void *memmove( void *dest, const void *src, size_t count ) { } return dest; } - +#endif #if 0 @@ -332,211 +321,16 @@ char *strncpy( char *strDest, const char *strSource, size_t count ) { s = strDest; while ( *strSource && count ) { - *s++ = *strSource++; + *s = *strSource; + s++; strSource++; count--; } while ( count-- ) { - *s++ = 0; + *s = '\0'; s++; } return strDest; } -double sqrt( double x ) { - float y; - float delta; - float maxError; - - if ( x <= 0 ) { - return 0; - } - - // initial guess - y = x / 2; - - // refine - maxError = x * 0.001; - - do { - delta = ( y * y ) - x; - y -= delta / ( 2 * y ); - } while ( delta > maxError || delta < -maxError ); - - return y; -} - - -float sintable[1024] = { -0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, -0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, -0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, -0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, -0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, -0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, -0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, -0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, -0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, -0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, -0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, -0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, -0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, -0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, -0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, -0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, -0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, -0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, -0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, -0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, -0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, -0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, -0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, -0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, -0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, -0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, -0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, -0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, -0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, -0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, -0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, -0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, -0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, -0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, -0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, -0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, -0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, -0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, -0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, -0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, -0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, -0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, -0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, -0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, -0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, -0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, -0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, -0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, -0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, -0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, -0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, -0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, -0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, -0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, -0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, -0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, -0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, -0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, -0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, -0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, -0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, -0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, -0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, -0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, -0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, -0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, -0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, -0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, -0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, -0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, -0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, -0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, -0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, -0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, -0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, -0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, -0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, -0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, -0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, -0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, -0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, -0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, -0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, -0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, -0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, -0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, -0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, -0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, -0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, -0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, -0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, -0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, -0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, -0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, -0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, -0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, -0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, -0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, -0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, -0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, -0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, -0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, -0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, -0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, -0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, -0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, -0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, -0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, -0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, -0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, -0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, -0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, -0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, -0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, -0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, -0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, -0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, -0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, -0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, -0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, -0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, -0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, -0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, -0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, -0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, -0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, -0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, -0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 -}; - -double sin( double x ) { - int index; - int quad; - - index = 1024 * x / (M_PI * 0.5); - quad = ( index >> 10 ) & 3; - index &= 1023; - switch ( quad ) { - case 0: - return sintable[index]; - case 1: - return sintable[1023-index]; - case 2: - return -sintable[index]; - case 3: - return -sintable[1023-index]; - } - return 0; -} - - -double cos( double x ) { - int index; - int quad; - - index = 1024 * x / (M_PI * 0.5); - quad = ( index >> 10 ) & 3; - index &= 1023; - switch ( quad ) { - case 3: - return sintable[index]; - case 0: - return sintable[1023-index]; - case 1: - return -sintable[index]; - case 2: - return -sintable[1023-index]; - } - return 0; -} - /* void create_acostable( void ) { @@ -764,6 +558,7 @@ double atan2( double y, double x ) { double tan( double x ) { return sin(x) / cos(x); } +#endif // BFP - Unpack music, memory management for ui_mp3decoder /* @@ -843,10 +638,10 @@ ivln2_l = 7.0526075433e-06; /* 0x36eca570 =1/ln2 tail*/ /* ================== -copysignf +Q_copysignf ================== */ -static float copysignf( float x, float y ) +static float Q_copysignf( float x, float y ) { unsigned int ix, iy; @@ -858,10 +653,10 @@ static float copysignf( float x, float y ) /* ================== -__scalbnf +Q__scalbnf ================== */ -static float __scalbnf( float x, int n ) +static float Q__scalbnf( float x, int n ) { int k, ix; @@ -884,16 +679,16 @@ static float __scalbnf( float x, int n ) k = k + n; if( n > 50000 || k > 0xfe ) - return huge * copysignf( huge, x ); /* overflow */ + return huge * Q_copysignf( huge, x ); /* overflow */ if ( n < -50000 ) - return tiny * copysignf( tiny, x ); /*underflow*/ + return tiny * Q_copysignf( tiny, x ); /*underflow*/ if( k > 0 ) /* normal result */ { SET_FLOAT_WORD( x, ( ix & 0x807fffff ) | ( k << 23 ) ); return x; } if( k <= -25 ) - return tiny * copysignf( tiny, x ); /*underflow*/ + return tiny * Q_copysignf( tiny, x ); /*underflow*/ k += 25; /* subnormal result */ SET_FLOAT_WORD( x, ( ix & 0x807fffff ) | ( k << 23 ) ); @@ -902,10 +697,10 @@ static float __scalbnf( float x, int n ) /* ================== -pow +mp3dec_pow ================== */ -float pow( float x, float y ) +float mp3dec_pow( float x, float y ) { float z, ax, z_h, z_l, p_h, p_l; float y1, t1, t2, r, s, t, u, v, w; @@ -1150,16 +945,17 @@ float pow( float x, float y ) j += (n << 23 ); if( ( j >> 23 ) <= 0 ) - z = __scalbnf( z, n ); /* subnormal output */ + z = Q__scalbnf( z, n ); /* subnormal output */ else SET_FLOAT_WORD( z, j ); return s * z; } -#endif - +// BFP - End of the implementation of unpack music, memory management for ui_mp3decoder +//================================================================================== +#ifndef _MSC_VER static int randSeed = 0; void srand( unsigned seed ) { @@ -1205,7 +1001,7 @@ double atof( const char *string ) { c = string[0]; if ( c != '.' ) { do { - c = *string++; + c = *string; string++; if ( c < '0' || c > '9' ) { break; } @@ -1222,7 +1018,7 @@ double atof( const char *string ) { fraction = 0.1; do { - c = *string++; + c = *string; string++; if ( c < '0' || c > '9' ) { break; } @@ -1237,19 +1033,19 @@ double atof( const char *string ) { return value * sign; } +#endif -double _atof( const char **stringPtr ) { - const char *string; - float sign; - float value; - int c = '0'; // bk001211 - uninitialized use possible - string = *stringPtr; +#if defined ( Q3_VM ) +int atoi( const char *string ) { + int sign; + int value; + int c; + // skip whitespace while ( *string <= ' ' ) { if ( !*string ) { - *stringPtr = string; return 0; } string++; @@ -1272,149 +1068,501 @@ double _atof( const char **stringPtr ) { // read digits value = 0; - if ( string[0] != '.' ) { - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; + do { + c = *string; string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + +int abs( int n ) { + return n < 0 ? -n : n; +} + +double fabs( double x ) { + return x < 0 ? -x : x; +} +#endif + + +int replace_s( char * str1, char * str2, char * src, int max_len ) +{ + int count = 0; // replace count + int len1, len2, d; + char *match, *s0, *s1, *s2, *max; + + match = strstr( src, str1 ); + + if ( !match ) + return count; + + len1 = (int)strlen( str1 ); + len2 = (int)strlen( str2 ); + d = len2-len1; + + if ( d > 0 ) // expand and replace mode + { + max = src + max_len; + src += strlen( src ); + + do + { + // expand source string + s1 = src; + src += d; + if ( src >= max ) + return count; + s2 = src; + + s0 = match + len1; + + while ( s1 >= s0 ) { + *s2-- = *s1--; } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - } - // check for decimal point - if ( c == '.' ) { - double fraction; + // replace match + s2 = str2; + while ( *s2 ) { + *match = *s2; match++; s2++; + } + match = strstr ( match, str1 ); - fraction = 0.1; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; + count++; + } + while ( match ); + + return count; + } + else + if ( d < 0 ) // shrink and replace mode + { + do + { + // shrink source string + s1 = match + len1; + s2 = match + len2; + while ( (*s2++ = *s1++) != 0 ); + + //replace match + s2 = str2; + while ( *s2 ) { + *match = *s2; + match++; s2++; } - c -= '0'; - value += c * fraction; - fraction *= 0.1; - } while ( 1 ); + match = strstr ( match, str1 ); + + count++; + } + while ( match ); + + return count; } + else + do // just replace match + { + s2 = str2; + while ( *s2 ) { + *match = *s2; + match++; s2++; + } - // not handling 10e10 notation... - *stringPtr = string; + match = strstr ( match, str1 ); + count++; + } + while ( match ); - return value * sign; + return count; } -// bk001120 - presumably needed for Mac -//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) - -// bk001127 - undid undo -#if defined ( Q3_VM ) -int atoi( const char *string ) { - int sign; - int value; - int c; +qboolean replace1( const char match, const char replace, char *str ) +{ + qboolean res = qfalse; + if ( !str ) + return res; - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; + while ( *str ) { + if ( *str == match ) { + *str = replace; + res = qtrue; } - string++; + str++; } - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; + return res; +} + + +char *strtok( char *strToken, const char *strDelimit ) { + static char *lastStr = NULL; + + const char *delimit = strDelimit; + int loop = 1; + + // check some state + if( !strToken ) + strToken = lastStr; // use the last string given then + + // if there are no delimiters or no string to work with, bail + if( !strToken /*|| !strDelimit */ ) + return NULL; + + // first up we want to skip all delimiters at the start + // and mark our place to return + while( *strToken && loop ) + { + loop = 0; + + // check all delimiters, we must find _one_ to continue the outside loop + for( delimit = strDelimit ; *delimit ; delimit++ ) + { + if( *strToken == *delimit ) + { + strToken++; + loop = 1; + break; + } + } } - // read digits - value = 0; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; + // strToken now points to the first non-delimiter found + // now we want to find the next delimiter to terminate on + for( loop=1, lastStr=strToken ; *lastStr && loop ; lastStr++ ) + { + // check all delimiters + for( delimit=strDelimit ; *delimit ; delimit++ ) + { + if( *delimit == *lastStr ) + { + loop = 0; + break; + } } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); + } - // not handling 10e10 notation... + // if the loop aborted then the token needs termination + if( !loop ) + { + *(lastStr-1) = '\0'; - return value * sign; + if ( '\0' == *lastStr ) + lastStr = NULL; + } + else + { + lastStr = NULL; // clean up for the next call + } + + return strToken; } -int _atoi( const char **stringPtr ) { - int sign; - int value; +char *BG_StripColor( char *string ) { + char *d; + char *s; int c; - const char *string; - string = *stringPtr; + s = string; + d = string; + while ((c = *s) != 0 ) + { + if ( Q_IsColorString( s ) ) + s++; + else { + *d = c; d++; + } + s++; + } + *d = '\0'; + return string; +} - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; + +char *EncodedString( const char *in ) +{ + static const char hextab[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; + static char buf[16384]; + unsigned int c; + char *out; + + if ( !in ) { + buf[0] = '\0'; + return buf; + } + + out = buf; + + while ( ( c = *in++ ) != '\0' ) { + if ( c == '#' ) { + *out++ = '#'; + *out++ = '#'; + } else + if ( c > 127 || c == '%' ) { + *out++ = '#'; + *out++ = hextab [ (c & 0xF0) >> 4 ]; + *out++ = hextab [ (c & 0x0F) ]; + } else { + *out++ = c; } - string++; } + *out = '\0'; + return (char *)buf; +} - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; + +static int hex2dec ( char chr ) { + switch ( chr ) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': return 10; + case 'A': return 10; + case 'b': return 11; + case 'B': return 11; + case 'c': return 12; + case 'C': return 12; + case 'd': return 13; + case 'D': return 13; + case 'e': return 14; + case 'E': return 14; + case 'f': return 15; + case 'F': return 15; } + return 0; +} - // read digits - value = 0; + +char *DecodedString( const char *in ) +{ + static char buf[16384]; + char *out, c1, c2; + + if ( !in ) { + buf[0] = '\0'; + return (char *)buf; + } + + out = buf; + + while ( *in ) { + // new encoding + if ( *in == '#' && (c1 = *(in+1)) != 0 && (c2 = *(in+2)) != 0 && + (( c1 >= '0' && c1 <= '9' ) || ( c1 >= 'a' && c1 <= 'f' )) && + (( c2 >= '0' && c2 <= '9' ) || ( c2 >= 'a' && c2 <= 'f' ))) { + *out++ = hex2dec( c1 ) * 16 + hex2dec( c2 ); + in += 3; + } else if ( *in == '#' && *(in+1) == '#' ) { + *out++ = '#'; + in += 2; + } else { + *out++ = *in++; + } + } + + *out = '\0'; + + return (char *)buf; +} + + +// a bit faster string compare function +int BG_stricmp( const char *s1, const char *s2 ) { + int c1, c2; do { - c = *string++; - if ( c < '0' || c > '9' ) { + c1 = locase[(unsigned char)*s1]; s1++; + c2 = locase[(unsigned char)*s2]; s2++; + if (c1 != c2) + return ((c1 < c2) ? -1 : 1); + } while ( c1 ); + return 0; // strings are equal +} + + +char *Q_stristr( const char * str1, const char * str2 ) +{ + char *cp = (char *) str1; + char *s1, *s2; + + if ( !*str2 ) + return( (char *)str1 ); + + while ( *cp ) + { + s1 = cp; + s2 = (char *) str2; + + while ( *s1 && locase[(unsigned char)*s1] == locase[(unsigned char)*s2] ) + { + s1++; + s2++; + } + + if ( !*s2 ) + return( cp ); + + cp++; + } + + return( NULL ); +} + + +/* +=========== +BG_CleanName +============ +*/ +void BG_CleanName( const char *in, char *out, int outSize, const char *blankString ) { + 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; } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - // not handling 10e10 notation... + // don't allow leading spaces + if( *p == '\0' && ch <= ' ' ) { + continue; + } - *stringPtr = string; + // check colors + if( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if( !*in ) { + break; + } - return value * sign; + // 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; + } + + // let's keep it in printable range + if ( ch < ' ' || ch > 126 ) { + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) { + spaces++; + if( spaces > 2 ) { + continue; + } + } + else { + spaces = 0; + } + + if( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = '\0'; + + if ( blankString ) { + // don't allow empty names + if( *p == '\0' || colorlessLen == 0 ) { + Q_strncpyz( p, blankString, outSize ); + } + } } -int abs( int n ) { - return n < 0 ? -n : n; + +/* +=================== +Q_strcpy + +string copy, without any checks +=================== +*/ +void Q_strcpy( char *dst, const char *src ) +{ + char c; + while ( (c = *src) != '\0' ) + { + *dst = c; dst++; src++; + } + *dst = '\0'; } -double fabs( double x ) { - return x < 0 ? -x : x; + +char *Q_stradd( char *dst, const char *src ) +{ + char c; + while ( (c = *src) != '\0' ) + { + *dst = c; + dst++; + src++; + } + *dst = '\0'; + return dst; } +int Q_strlen( const char *s ) +{ + const char *b = s; + while ( *s != '\0' ) + s++; + return s - b; +} + + +qboolean BigEndian( void ) +{ + const char *s = { "123" }; + int *i; + i = (void*)s; + if ( *i != 0x00333231 ) + return qtrue; + else + return qfalse; +} //========================================================= @@ -1429,61 +1577,73 @@ double fabs( double x ) { #define SHORTINT 0x00000040 /* short integer */ #define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ #define FPT 0x00000100 /* floating point number */ +#define REDUCE 0x00000200 /* extension: do not emit anything if value is zero */ #define to_digit(c) ((c) - '0') #define is_digit(c) ((unsigned)to_digit(c) <= 9) #define to_char(n) ((n) + '0') -void AddInt( char **buf_p, int val, int width, int flags ) { +static void AddInt( char **buf_p, int val, int width, int flags ) { char text[32]; int digits; int signedVal; char *buf; + if ( flags & REDUCE && val == 0 ) + return; + digits = 0; signedVal = val; if ( val < 0 ) { val = -val; } do { - text[digits++] = '0' + val % 10; + text[digits] = '0' + val % 10; + digits++; val /= 10; } while ( val ); if ( signedVal < 0 ) { - text[digits++] = '-'; + text[digits] = '-'; + digits++; } buf = *buf_p; if( !( flags & LADJUST ) ) { while ( digits < width ) { - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + *buf = ( flags & ZEROPAD ) ? '0' : ' '; + buf++; width--; } } - while ( digits-- ) { - *buf++ = text[digits]; + while ( digits-- > 0 ) { + *buf = text[digits]; buf++; width--; } if( flags & LADJUST ) { - while ( width-- ) { - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + while ( width > 0 ) { + *buf = ( flags & ZEROPAD ) ? '0' : ' '; + buf++; + width--; } } *buf_p = buf; } -void AddFloat( char **buf_p, float fval, int width, int prec ) { +static void AddFloat( char **buf_p, float fval, int width, int prec, int reduce ) { char text[32]; int digits; float signedVal; char *buf; int val; + if ( reduce && fval == 0.0f ) + return; + // get the sign signedVal = fval; if ( fval < 0 ) { @@ -1494,50 +1654,61 @@ void AddFloat( char **buf_p, float fval, int width, int prec ) { digits = 0; val = (int)fval; do { - text[digits++] = '0' + val % 10; + text[digits] = '0' + val % 10; + digits++; val /= 10; } while ( val ); if ( signedVal < 0 ) { - text[digits++] = '-'; + text[digits] = '-'; + digits++; } buf = *buf_p; + // fix precisiion + if ( prec < 0 ) { + prec = 6; + } + + if ( prec ) { + width -= prec + 1; + } + // end + while ( digits < width ) { - *buf++ = ' '; + *buf = ' '; buf++; width--; } - while ( digits-- ) { - *buf++ = text[digits]; + while ( digits-- > 0 ) { + *buf = text[digits]; buf++; } *buf_p = buf; - if (prec < 0) - prec = 6; // write the fraction digits = 0; while (digits < prec) { fval -= (int) fval; fval *= 10.0; val = (int) fval; - text[digits++] = '0' + val % 10; + text[digits] = '0' + val % 10; + digits++; } if (digits > 0) { buf = *buf_p; - *buf++ = '.'; + *buf = '.'; buf++; for (prec = 0; prec < digits; prec++) { - *buf++ = text[prec]; + *buf = text[prec]; buf++; } *buf_p = buf; } } -void AddString( char **buf_p, char *string, int width, int prec ) { +static void AddString( char **buf_p, const char *string, int width, int prec ) { int size; char *buf; @@ -1556,50 +1727,51 @@ void AddString( char **buf_p, char *string, int width, int prec ) { } } else { - size = strlen( string ); + size = Q_strlen( string ); } width -= size; while( size-- ) { - *buf++ = *string++; + *buf = *string; + buf++; string++; } while( width-- > 0 ) { - *buf++ = ' '; + *buf = ' '; buf++; } *buf_p = buf; } + /* -vsprintf +Q_vsprintf I'm not going to support a bunch of the more arcane stuff in here -just to keep it simpler. For example, the '*' and '$' are not +just to keep it simpler. For example, the '$' is not currently supported. I've tried to make it so that it will just parse and ignore formats we don't support. + +returns: number of char written without ending '\0' */ -int vsprintf( char *buffer, const char *fmt, va_list argptr ) { - int *arg; +int Q_vsprintf( char *buffer, const char *fmt, va_list ap ) { char *buf_p; char ch; int flags; int width; int prec; int n; - char sign; buf_p = buffer; - arg = (int *)argptr; while( qtrue ) { // run through the format string until we hit a '%' or '\0' - for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { - *buf_p++ = ch; + for ( /*ch = *fmt */; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { + *buf_p = ch; buf_p++; } if ( ch == '\0' ) { - goto done; + break; } // skip over the '%' @@ -1609,22 +1781,28 @@ int vsprintf( char *buffer, const char *fmt, va_list argptr ) { flags = 0; width = 0; prec = -1; - sign = '\0'; - rflag: - ch = *fmt++; + ch = *fmt; fmt++; reswitch: switch( ch ) { + //case ' ': case '-': flags |= LADJUST; goto rflag; case '.': - n = 0; - while( is_digit( ( ch = *fmt++ ) ) ) { - n = 10 * n + ( ch - '0' ); + if ( *fmt == '*' ) { + fmt++; + n = va_arg( ap, int ); + prec = n < 0 ? -1 : n; + goto rflag; + } else { + n = 0; + while( is_digit( ( ch = *fmt++ ) ) ) { + n = 10 * n + ( ch - '0' ); + } + prec = n < 0 ? -1 : n; + goto reswitch; } - prec = n < 0 ? -1 : n; - goto reswitch; case '0': flags |= ZEROPAD; goto rflag; @@ -1640,78 +1818,287 @@ int vsprintf( char *buffer, const char *fmt, va_list argptr ) { n = 0; do { n = 10 * n + ( ch - '0' ); - ch = *fmt++; + ch = *fmt; fmt++; } while( is_digit( ch ) ); width = n; goto reswitch; + case '*': + width = va_arg( ap, int ); + goto rflag; case 'c': - *buf_p++ = (char)*arg; - arg++; + *buf_p = (char)va_arg( ap, int ); buf_p++; break; case 'd': case 'i': - AddInt( &buf_p, *arg, width, flags ); - arg++; + AddInt( &buf_p, va_arg( ap, int ), width, flags ); break; case 'f': - AddFloat( &buf_p, *(double *)arg, width, prec ); -#ifdef __LCC__ - arg += 1; // everything is 32 bit in my compiler -#else - arg += 2; -#endif + AddFloat( &buf_p, va_arg( ap, double ), width, prec, flags & REDUCE ); break; case 's': - AddString( &buf_p, (char *)*arg, width, prec ); - arg++; + AddString( &buf_p, va_arg( ap, char * ), width, prec ); break; case '%': - *buf_p++ = ch; + *buf_p = ch; buf_p++; break; + // edawn extension: + case 'R': + flags |= REDUCE; + goto rflag; default: - *buf_p++ = (char)*arg; - arg++; + *buf_p = (char)va_arg( ap, int ); buf_p++; + break; + } // switch ( ch ) + } // while ( qtrue ) + + *buf_p = '\0'; + return buf_p - buffer; +} + + +int BG_sprintf( char *buf, const char *format, ... ) +{ + int len; + va_list argptr; + va_start( argptr, format ); + len = Q_vsprintf( buf, format, argptr ); + va_end( argptr ); + return len; +} + + +static int _atoi( const char **stringPtr ) +{ + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + if ( !*string ) + return 0; + + // check sign + switch ( *string ) + { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do + { + c = *string; + if ( c < '0' || c > '9' ) + { break; } + c -= '0'; + value = value * 10 + c; + string++; + } + while ( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + + +static float _atof( const char **stringPtr ) +{ + const char *string; + float sign; + float value; + float fraction; + int c = '0'; // uninitialized use possible + + string = *stringPtr; + + if ( !*string ) + return 0; + + // check sign + switch ( *string ) + { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; } -done: - *buf_p = 0; - return buf_p - buffer; + // read digits + value = 0; + if ( *string != '.' ) + { + do + { + c = *string; + if ( c < '0' || c > '9' ) + { + break; + } + c -= '0'; + value = value * 10 + c; + string++; + } + while ( 1 ); + } + + // check for decimal point + if ( *string == '.' ) + { + fraction = 0.1f; + string++; + do + { + c = *string; + if ( c < '0' || c > '9' ) + { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1f; + string++; + } + while ( 1 ); + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +static void _atos( const char **stringPtr, char *buffer, int delimiter, int width ) +{ + const char *string; + + string = *stringPtr; + + if ( !delimiter ) + { + // skip whitespace + while ( *string && *string != ' ' && *string != '\t' && width-- > 0 ) + { + *buffer = *string; + buffer++; + string++; + } + } + else while ( *string && *string != delimiter && width-- > 0 ) + { + *buffer = *string; + buffer++; + string++; + } + + *stringPtr = string; + + *buffer = '\0'; } -/* this is really crappy */ -int sscanf( const char *buffer, const char *fmt, ... ) { - int cmd; - int **arg; - int count; - arg = (int **)&fmt + 1; +int Q_sscanf( const char *buffer, const char *fmt, ... ) +{ + va_list ap; + int count; + int width; + int cmd; + const char *p; + + va_start( ap, fmt ); count = 0; - while ( *fmt ) { - if ( fmt[0] != '%' ) { + while ( *fmt ) + { + // single whitespace char validates any quantity of whitespace characters + // extracted from the stream (including none) + if ( *fmt == ' ' || *fmt == '\t' || *fmt == '\n' ) + { + while ( *buffer == ' ' || *buffer == '\t' || *buffer == '\n' ) + buffer++; + fmt++; + } + + if ( *fmt != '%' ) + { + if ( *fmt != *buffer ) + break; + + buffer++; fmt++; continue; } - cmd = fmt[1]; - fmt += 2; + width = fmt[1]; + fmt++; // % + if ( width >= '0' && width <= '9' ) + { + width -= '0'; // valid width; + fmt++; // ['0'..'9'] + cmd = *fmt; + } + else + { + cmd = width; + width = 1024; // some assumption + } + + p = buffer; + + fmt++; // switch to delimiter? + + //printf( "cmd=%c buffer=%s width=%i delim='%c'\n", cmd, buffer, width, *fmt ); - switch ( cmd ) { + switch ( cmd ) + { case 'i': case 'd': case 'u': - **arg = _atoi( &buffer ); + *(va_arg(ap, int *)) = _atoi( &buffer ); break; case 'f': - *(float *)*arg = _atof( &buffer ); + *(va_arg(ap, float *)) = _atof( &buffer ); break; + case 'c': + *(va_arg(ap, char *)) = *buffer; buffer++; + break; + case 's': + _atos( &buffer, va_arg(ap, char *), *fmt, width ); + break; + default: + return count; } - arg++; + + if ( p != buffer ) + count++; + else + break; } + va_end( ap ); return count; } - -#endif diff --git a/source/game/bg_lib.h b/source/game/bg_lib.h index f7c4545..44c5602 100644 --- a/source/game/bg_lib.h +++ b/source/game/bg_lib.h @@ -25,6 +25,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // This file is NOT included on native builds typedef int size_t; +typedef int intptr_t; typedef char * va_list; #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) @@ -54,7 +55,7 @@ void srand( unsigned seed ); int rand( void ); // String functions -int strlen( const char *string ); +size_t strlen( const char *string ); char *strcat( char *strDestination, const char *strSource ); char *strcpy( char *strDestination, const char *strSource ); int strcmp( const char *string1, const char *string2 ); @@ -65,12 +66,10 @@ int tolower( int c ); int toupper( int c ); double atof( const char *string ); -double _atof( const char **stringPtr ); int atoi( const char *string ); -int _atoi( const char **stringPtr ); -int vsprintf( char *buffer, const char *fmt, va_list argptr ); -int sscanf( const char *buffer, const char *fmt, ... ); +int Q_vsprintf( char *buffer, const char *fmt, va_list argptr ); +int Q_sscanf( const char *buffer, const char *fmt, ... ); // Memory functions void *memmove( void *dest, const void *src, size_t count ); @@ -88,5 +87,3 @@ double tan( double x ); int abs( int n ); double fabs( double x ); double acos( double x ); -float pow( float x, float y ); // BFP - Unpack music, memory management for ui_mp3decoder - diff --git a/source/game/bg_local.h b/source/game/bg_local.h index 389683d..a6dbd9d 100644 --- a/source/game/bg_local.h +++ b/source/game/bg_local.h @@ -26,7 +26,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define STEPSIZE 18 -#define JUMP_VELOCITY 270 +#define JUMP_VELOCITY 600 // BFP - Before 270 #define TIMER_LAND 130 #define TIMER_GESTURE (34*66+50) @@ -60,7 +60,6 @@ extern pml_t pml; extern float pm_stopspeed; extern float pm_duckScale; extern float pm_swimScale; -extern float pm_wadeScale; extern float pm_accelerate; extern float pm_airaccelerate; @@ -80,4 +79,5 @@ void PM_AddEvent( int newEvent ); qboolean PM_SlideMove( qboolean gravity ); void PM_StepSlideMove( qboolean gravity ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); diff --git a/source/game/bg_meansofdeath.h b/source/game/bg_meansofdeath.h new file mode 100644 index 0000000..e019de0 --- /dev/null +++ b/source/game/bg_meansofdeath.h @@ -0,0 +1,42 @@ +#ifdef MOD_ENUMS + #define DECLARE_MOD( mod ) mod +#endif + +#ifdef MOD_STRINGS + #define DECLARE_MOD( mod ) #mod +#endif + +// BFP - NOTE: Some original BFP meansOfDeath aren't applied correctly like this replica does +DECLARE_MOD( MOD_UNKNOWN ), +DECLARE_MOD( MOD_SHOTGUN ), +DECLARE_MOD( MOD_GAUNTLET ), +DECLARE_MOD( MOD_MACHINEGUN ), +DECLARE_MOD( MOD_GRENADE ), +DECLARE_MOD( MOD_GRENADE_SPLASH ), +DECLARE_MOD( MOD_ROCKET ), +DECLARE_MOD( MOD_ROCKET_SPLASH ), +DECLARE_MOD( MOD_PLASMA ), +DECLARE_MOD( MOD_PLASMA_SPLASH ), +DECLARE_MOD( MOD_RAILGUN ), +DECLARE_MOD( MOD_LIGHTNING ), +DECLARE_MOD( MOD_BFG ), +DECLARE_MOD( MOD_BFG_SPLASH ), +DECLARE_MOD( MOD_WATER ), +DECLARE_MOD( MOD_SLIME ), +DECLARE_MOD( MOD_LAVA ), +DECLARE_MOD( MOD_CRUSH ), +DECLARE_MOD( MOD_TELEFRAG ), +DECLARE_MOD( MOD_FALLING ), +DECLARE_MOD( MOD_SUICIDE ), +DECLARE_MOD( MOD_TARGET_LASER ), +DECLARE_MOD( MOD_TRIGGER_HURT ), +DECLARE_MOD( MOD_KI_ATTACK ), // BFP - Ki attack kill message +DECLARE_MOD( MOD_MELEE ), // BFP - Melee +DECLARE_MOD( MOD_ILLEGAL_PLAYER_MODEL ), // BFP - Illegal player model +DECLARE_MOD( MOD_GRAPPLE ) + +#ifdef MOD_ENUMS + , DECLARE_MOD( MOD_MAX ) +#endif + +#undef DECLARE_MOD diff --git a/source/game/bg_misc.c b/source/game/bg_misc.c index 812b639..c6ac948 100644 --- a/source/game/bg_misc.c +++ b/source/game/bg_misc.c @@ -62,6 +62,7 @@ gitem_t bg_itemlist[] = // ARMOR // +// BFP - 1 /*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -79,6 +80,7 @@ gitem_t bg_itemlist[] = /* sounds */ "" }, +// BFP - 2 /*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -95,6 +97,7 @@ gitem_t bg_itemlist[] = /* sounds */ "" }, +// BFP - 3 /*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -114,6 +117,7 @@ gitem_t bg_itemlist[] = // // health // +// BFP - 4 /*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -131,6 +135,7 @@ gitem_t bg_itemlist[] = /* sounds */ "" }, +// BFP - 5 /*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -148,6 +153,7 @@ gitem_t bg_itemlist[] = /* sounds */ "" }, +// BFP - 6 /*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -165,6 +171,7 @@ gitem_t bg_itemlist[] = /* sounds */ "" }, +// BFP - 7 /*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -186,7 +193,135 @@ gitem_t bg_itemlist[] = // // WEAPONS // +#define ENABLE_Q3_WEAPONS 1 +#if !ENABLE_Q3_WEAPONS +// BFP - TODO: IMPORTANT: Check config attack implementation, original BFP demo networking uses weapons from 8 until 12 +// BFP - 8 + { + "weapon", + "sound/misc/w_pkup.wav", + { "models/powerups/instant/quad.md3", 0, 0, 0 }, +/* icon */ "icons/largekiblast", +/* pickup */ "Weapon", + 0, + IT_WEAPON, + WP_NONE, +/* precache */ "", +/* sounds */ "" + }, +// BFP - 9 ... 116 + { "weapon", "sound/misc/w_pkup.wav", {"models/powerups/instant/quad.md3", 0, 0, 0}, "icons/largekiblast", "Weapon", 0, IT_WEAPON, WP_GAUNTLET, "", "" }, + { "weapon", "sound/misc/w_pkup.wav", {"models/powerups/instant/quad.md3", 0, 0, 0}, "icons/largekiblast", "Weapon", 0, IT_WEAPON, WP_SHOTGUN, "", "" }, + { "weapon", "sound/misc/w_pkup.wav", {"models/powerups/instant/quad.md3", 0, 0, 0}, "icons/largekiblast", "Weapon", 0, IT_WEAPON, WP_MACHINEGUN, "", "" }, + { "weapon", "sound/misc/w_pkup.wav", {"models/powerups/instant/quad.md3", 0, 0, 0}, "icons/largekiblast", "Weapon", 0, IT_WEAPON, WP_GRENADE_LAUNCHER, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, + { NULL, NULL, {NULL, NULL, 0, 0}, NULL, NULL, 0, 0, 0, "", "" }, +#else +// BFP - TODO: IMPORTANT: AFTER COMPLETING CONFIG ATTACK IMPLEMENTATION, +// REMOVE ALL Q3 WEAPONS AND KEEP ORIGINAL BFP ITEM INDEXES, MACRO INCLUDED /*QUAKED weapon_gauntlet (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -346,142 +481,14 @@ gitem_t bg_itemlist[] = /* precache */ "", /* sounds */ "" }, +#endif - // - // AMMO ITEMS - // - -/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_shells", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/shotgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_shotgun", -/* pickup */ "Shells", - 10, - IT_AMMO, - WP_SHOTGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_bullets", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/machinegunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_machinegun", -/* pickup */ "Bullets", - 50, - IT_AMMO, - WP_MACHINEGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_grenades", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/grenadeam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_grenade", -/* pickup */ "Grenades", - 5, - IT_AMMO, - WP_GRENADE_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_cells", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/plasmaam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_plasma", -/* pickup */ "Cells", - 30, - IT_AMMO, - WP_PLASMAGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_lightning", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/lightningam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_lightning", -/* pickup */ "Lightning", - 60, - IT_AMMO, - WP_LIGHTNING, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_rockets", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/rocketam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_rocket", -/* pickup */ "Rockets", - 5, - IT_AMMO, - WP_ROCKET_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, -/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_slugs", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/railgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_railgun", -/* pickup */ "Slugs", - 10, - IT_AMMO, - WP_RAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_bfg", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/bfgam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_bfg", -/* pickup */ "Bfg Ammo", - 15, - IT_AMMO, - WP_BFG, -/* precache */ "", -/* sounds */ "" - }, // // HOLDABLE ITEMS // +// BFP - 117 /*QUAKED holdable_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -497,6 +504,8 @@ gitem_t bg_itemlist[] = /* precache */ "", /* sounds */ "" }, + +// BFP - 118 /*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -518,6 +527,7 @@ gitem_t bg_itemlist[] = // // POWERUP ITEMS // +// BFP - 119 /*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -535,6 +545,7 @@ gitem_t bg_itemlist[] = /* sounds */ "sound/items/damage2.wav sound/items/damage3.wav" }, +// BFP - 120 /*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -547,11 +558,12 @@ gitem_t bg_itemlist[] = /* pickup */ "Battle Suit", 30, IT_POWERUP, - PW_BATTLESUIT, + 0, //PW_BATTLESUIT, // BFP - No battlesuit powerup /* precache */ "", /* sounds */ "sound/items/airout.wav sound/items/protect3.wav" }, +// BFP - 121 /*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -564,11 +576,12 @@ gitem_t bg_itemlist[] = /* pickup */ "Speed", 30, IT_POWERUP, - PW_HASTE, + 0, /* precache */ "", /* sounds */ "" }, +// BFP - 122 /*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { @@ -581,45 +594,12 @@ gitem_t bg_itemlist[] = /* pickup */ "Invisibility", 30, IT_POWERUP, - PW_INVIS, + 0, /* precache */ "", /* sounds */ "" }, -/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_regen", - "sound/items/regeneration.wav", - { "models/powerups/instant/regen.md3", - "models/powerups/instant/regen_ring.md3", - 0, 0 }, -/* icon */ "icons/regen", -/* pickup */ "Regeneration", - 30, - IT_POWERUP, - PW_REGEN, -/* precache */ "", -/* sounds */ "sound/items/regen.wav" - }, - -/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_flight", - "sound/items/flight.wav", - { "models/powerups/instant/flight.md3", - "models/powerups/instant/flight_ring.md3", - 0, 0 }, -/* icon */ "icons/flight", -/* pickup */ "Flight", - 60, - IT_POWERUP, - PW_FLIGHT, -/* precache */ "", -/* sounds */ "sound/items/flight.wav" - }, - +// BFP - 123 /*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) Only in CTF games */ @@ -637,6 +617,7 @@ Only in CTF games /* sounds */ "" }, +// BFP - 124 /*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) Only in CTF games */ @@ -747,7 +728,7 @@ grabbing them easier ============ */ qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { - vec3_t origin; + vec3_t origin = {0, 0, 0}; BG_EvaluateTrajectory( &item->pos, atTime, origin ); @@ -880,7 +861,7 @@ void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); break; case TR_SINE: - deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + deltaTime = ( ( atTime - tr->trTime ) % tr->trDuration ) / (float) tr->trDuration; phase = sin( deltaTime * M_PI * 2 ); VectorMA( tr->trBase, phase, tr->trDelta, result ); break; @@ -900,7 +881,7 @@ void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... break; default: - Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trType ); break; } } @@ -943,99 +924,17 @@ void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t resu result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... break; default: - Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trType ); break; } } -char *eventnames[] = { - "EV_NONE", - - "EV_FOOTSTEP", - "EV_FOOTSTEP_METAL", - "EV_FOOTSPLASH", - "EV_FOOTWADE", - "EV_SWIM", - - "EV_STEP_4", - "EV_STEP_8", - "EV_STEP_12", - "EV_STEP_16", - - "EV_FALL_SHORT", - "EV_FALL_MEDIUM", - "EV_FALL_FAR", - - "EV_JUMP_PAD", // boing sound at origin", jump sound on player - - "EV_JUMP", - "EV_WATER_TOUCH", // foot touches - "EV_WATER_LEAVE", // foot leaves - "EV_WATER_UNDER", // head touches - "EV_WATER_CLEAR", // head leaves - - "EV_ITEM_PICKUP", // normal item pickups are predictable - "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone - - "EV_NOAMMO", - "EV_CHANGE_WEAPON", - "EV_FIRE_WEAPON", - - "EV_USE_ITEM0", - "EV_USE_ITEM1", - "EV_USE_ITEM2", - "EV_USE_ITEM3", - "EV_USE_ITEM4", - "EV_USE_ITEM5", - "EV_USE_ITEM6", - "EV_USE_ITEM7", - "EV_USE_ITEM8", - "EV_USE_ITEM9", - "EV_USE_ITEM10", - "EV_USE_ITEM11", - "EV_USE_ITEM12", - "EV_USE_ITEM13", - "EV_USE_ITEM14", - "EV_USE_ITEM15", - - "EV_ITEM_RESPAWN", - "EV_ITEM_POP", - "EV_PLAYER_TELEPORT_IN", - "EV_PLAYER_TELEPORT_OUT", - - "EV_GRENADE_BOUNCE", // eventParm will be the soundindex - - "EV_GENERAL_SOUND", - "EV_GLOBAL_SOUND", // no attenuation - "EV_GLOBAL_TEAM_SOUND", - - "EV_BULLET_HIT_FLESH", - "EV_BULLET_HIT_WALL", - - "EV_MISSILE_HIT", - "EV_MISSILE_MISS", - "EV_MISSILE_MISS_METAL", - "EV_RAILTRAIL", - "EV_SHOTGUN", - "EV_BULLET", // otherEntity is the shooter - - "EV_PAIN", - "EV_DEATH1", - "EV_DEATH2", - "EV_DEATH3", - "EV_OBITUARY", - - "EV_POWERUP_QUAD", - "EV_POWERUP_BATTLESUIT", - "EV_POWERUP_REGEN", - - "EV_GIB_PLAYER", // gib a previously living player - "EV_SCOREPLUM", // score plum - - "EV_DEBUG_LINE", - "EV_STOPLOOPINGSOUND", - "EV_TAUNT" - +const char *eventnames[EV_MAX] = { + // BFP - Events are declared in bg_events.h file +#define EVENT_STRINGS + #include "bg_events.h" +#undef EVENT_STRINGS + "NULL" // avoid -Wpedantic warnings }; /* @@ -1046,9 +945,13 @@ Handles the sequence numbers =============== */ +#ifdef CGAME +void CG_StoreEvent( entity_event_t ev, int eventParm, int entityNum ); +#endif + void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); -void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { +void BG_AddPredictableEventToPlayerstate( entity_event_t newEvent, int eventParm, playerState_t *ps, int entityNum ) { #ifdef _DEBUG { @@ -1063,6 +966,11 @@ void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerSta } } #endif + +#ifdef CGAME + CG_StoreEvent( newEvent, eventParm, entityNum ); +#endif + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; ps->eventSequence++; @@ -1083,10 +991,13 @@ void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { return; } +// BFP - Flight status can be interacted to bounce pads +#if 0 // flying characters don't hit bounce pads if ( ps->powerups[PW_FLIGHT] ) { return; } +#endif // if we didn't hit this same jumppad the previous frame // then don't play the event sound again if we are in a fat trigger @@ -1099,13 +1010,16 @@ void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { } else { effectNum = 1; } - BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps ); + BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps, -1 ); } // remember hitting this jumppad this frame ps->jumppad_ent = jumppad->number; ps->jumppad_frame = ps->pmove_framecount; // give the player the velocity from the jumppad VectorCopy( jumppad->origin2, ps->velocity ); + + // BFP - Keep the speed (take a look on bg_pmove.c about PM_AirMove where handles that PMF flag) + ps->pm_flags &= ~PMF_AIR_GRAVITY; } /* diff --git a/source/game/bg_pmove.c b/source/game/bg_pmove.c index 655bb35..1469a80 100644 --- a/source/game/bg_pmove.c +++ b/source/game/bg_pmove.c @@ -33,18 +33,17 @@ pml_t pml; // movement parameters float pm_stopspeed = 100.0f; float pm_duckScale = 0.25f; -float pm_swimScale = 0.50f; -float pm_wadeScale = 0.70f; +// float pm_swimScale = 0.50f; // BFP - No water speed slowness float pm_accelerate = 10.0f; -float pm_airaccelerate = 1.0f; -float pm_wateraccelerate = 4.0f; -float pm_flyaccelerate = 8.0f; +float pm_airaccelerate = 4.5f; // BFP - Add more air acceleration to handle user movement intentions, before 1.0f +float pm_wateraccelerate = 20.0f; // BFP - Add more water acceleration to handle user movement intentions, before 4.0f +float pm_flyaccelerate = 2.0f; // BFP - Add less flight acceleration, before 8.0f float pm_friction = 6.0f; float pm_waterfriction = 1.0f; -float pm_flightfriction = 3.0f; -float pm_spectatorfriction = 5.0f; +float pm_flightfriction = 2.0f; // BFP - Add less flight friction, before 3.0f +float pm_spectatorfriction = 2.0f; // BFP - Add less spectator movement friction, before 5.0f int c_pmove = 0; @@ -55,7 +54,7 @@ PM_AddEvent =============== */ void PM_AddEvent( int newEvent ) { - BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps, -1 ); } /* @@ -133,6 +132,369 @@ static void PM_ForceLegsAnim( int anim ) { PM_StartLegsAnim( anim ); } +/* +=============== +PM_SetupBFP_weaponCfg +=============== +*/ +static void PM_SetupBFP_weaponCfg( void ) { // BFP - TODO: Set initialized bfp_weapon.cfg properties to every weapon + // BFP - Testing randomWeaponTime, loopingAnim, noAttackAnim, + // chargeAttack, chargeAutoFire, movementPenalty + switch ( pm->ps->weapon ) { + case WP_GAUNTLET: + pm->attackType = ATK_HITSCAN; + pm->weaponTime = 100; + pm->kiCostAsPct = qtrue; + pm->kiPct = .010; + pm->kiCost = 0; + pm->chargeAttack = qfalse; + pm->chargeAutoFire = qfalse; + pm->loopingAnim = qtrue; + pm->noAttackAnim = qfalse; + pm->railTrail = qfalse; + return; + case WP_MACHINEGUN: + pm->attackType = ATK_HITSCAN; + pm->weaponTime = 50; + pm->randomWeaponTime = 100; + pm->kiCostAsPct = qtrue; + pm->kiPct = .010; + pm->kiCost = 0; + pm->chargeAttack = qfalse; + pm->chargeAutoFire = qfalse; + pm->loopingAnim = qtrue; + pm->noAttackAnim = qfalse; + pm->railTrail = qfalse; + return; + case WP_SHOTGUN: + pm->attackType = ATK_FORCEFIELD; + pm->weaponTime = 200; + pm->kiCostAsPct = qfalse; + pm->kiPct = 0; + pm->kiCost = 200; + pm->minCharge = 4; + pm->maxCharge = 0; + pm->chargeAttack = qtrue; + pm->chargeAutoFire = qtrue; + pm->loopingAnim = qfalse; + pm->noAttackAnim = qfalse; + pm->movementPenalty = 2 * 1000; + return; + case WP_GRENADE_LAUNCHER: + case WP_ROCKET_LAUNCHER: + pm->attackType = ATK_MISSILE; + pm->weaponTime = 1000; + pm->kiCostAsPct = qfalse; + pm->kiPct = 0; + pm->kiCost = 10; + pm->chargeAttack = qfalse; + pm->chargeAutoFire = qfalse; + pm->loopingAnim = qfalse; + pm->noAttackAnim = qfalse; + return; + case WP_BFG: + pm->attackType = ATK_MISSILE; + pm->weaponTime = 1000; + pm->kiCostAsPct = qfalse; + pm->kiPct = 0; + pm->kiCost = 250; + pm->minCharge = 2; + pm->maxCharge = 6; + pm->chargeAttack = qtrue; + pm->chargeAutoFire = qfalse; + pm->loopingAnim = qfalse; + pm->noAttackAnim = qfalse; + return; + case WP_RAILGUN: + pm->attackType = ATK_HITSCAN; + pm->weaponTime = 1500; + pm->kiCostAsPct = qfalse; + pm->kiPct = 0; + pm->kiCost = 500; + pm->chargeAttack = qfalse; + pm->chargeAutoFire = qfalse; + pm->loopingAnim = qfalse; + pm->noAttackAnim = qfalse; + pm->railTrail = qtrue; + return; + case WP_PLASMAGUN: + pm->attackType = ATK_RDMISSILE; + pm->weaponTime = 1250; + pm->kiCostAsPct = qfalse; + pm->kiPct = 0; + pm->kiCost = 200; + pm->chargeAttack = qtrue; + pm->chargeAutoFire = qfalse; + pm->minCharge = 2; + pm->maxCharge = 6; + pm->loopingAnim = qfalse; + pm->noAttackAnim = qfalse; + return; + case WP_LIGHTNING: + pm->attackType = ATK_HITSCAN; + pm->weaponTime = 50; + pm->kiCostAsPct = qtrue; + pm->kiPct = .015; + pm->kiCost = 0; + pm->chargeAttack = qfalse; + pm->chargeAutoFire = qfalse; + pm->loopingAnim = qfalse; + pm->noAttackAnim = qfalse; + pm->railTrail = qfalse; + return; + case WP_GRAPPLING_HOOK: + pm->attackType = ATK_BEAM; + pm->weaponTime = 500; + pm->kiCostAsPct = qfalse; + pm->kiPct = 0; + pm->kiCost = 250; + pm->chargeAttack = qtrue; + pm->chargeAutoFire = qfalse; + pm->minCharge = 2; + pm->maxCharge = 6; + pm->loopingAnim = qfalse; + pm->noAttackAnim = qfalse; + } +} + + +/* +================ +PM_IsInKiAttackState + +Returns qtrue when the player is actively attacking +(ki attack animation phase, or rapid-fire cooldown) +================ +*/ +static qboolean PM_IsInKiAttackState( void ) { // BFP - Ki attack state + switch ( pm->ps->weaponstate ) { + case WEAPON_ACTIVE: + case WEAPON_BEAMSTRUGGLE: + case WEAPON_FIRING: + return qtrue; + case WEAPON_READY: + // Rapid-fire weapons set weaponstate back to READY immediately + // but weaponTime stays > 0 during cooldown. Keep ki attack anim running + return ( pm->ps->weaponTime > 0 ) ? qtrue : qfalse; + default: + return qfalse; + } +} + +/* +================ +PM_AttackAnim + +Handles attack animations through noAttackAnim and loopingAnim +================ +*/ +static void PM_AttackAnim( int anim, int prepareAnim ) { // BFP - Handles attack animations through noAttackAnim and loopingAnim + // noAttackAnim + if ( pm->noAttackAnim ) { + if ( !PM_IsInKiAttackState() && pm->ps->weaponstate != WEAPON_FIRING ) { + PM_ContinueTorsoAnim( prepareAnim ); + return; + } + PM_ContinueTorsoAnim( anim ); + return; + } + + // loopingAnim + if ( !pm->loopingAnim // chargeAutoFire animates this way + && pm->chargeAutoFire && pm->ps->generic1 < 3 ) { + PM_ContinueTorsoAnim( prepareAnim ); + return; + } + if ( !pm->loopingAnim // chargeAutoFire animates this way + && pm->chargeAutoFire && pm->ps->generic1 >= 3 ) { + PM_ContinueTorsoAnim( anim ); + return; + } + if ( !pm->loopingAnim + && ( !PM_IsInKiAttackState() || pm->ps->weaponstate == WEAPON_FIRING ) ) { + PM_ContinueTorsoAnim( prepareAnim ); + return; + } + if ( PM_IsInKiAttackState() || pm->loopingAnim ) { + PM_ContinueTorsoAnim( anim ); + } +} + + +/* +================== +PM_KiAttackTorsoAnim + +BFP - TODO: When implementing ki attacks, look up about the properties of the ki attacks from cfg and correct animation changes if required +And tweak pmove_t struct, so we can handle that on g_active.c (like meleeHit), adding: +attackType ("beam", "hitscan", "missile", "rdmissile", "sbeam" or "forcefield"), // type of attack +randomWeaponTime (int, number of miliseconds), // random weapon time, maybe the max msec range of the random value +chargeAttack (int / qboolean), // charging yes or no +chargeAutoFire (int / qboolean), // even if it's charging the ki attack, fire +minCharge (int [0-6]), // min charge points +maxCharge (int [0-6]), // max charge points +loopingAnim (int / qboolean), // Maybe it's: use PM_ContinueTorsoAnim, if not: PM_StartTorsoAnim +noAttackAnim (int / qboolean), // no animation strike yes or no +priority (int [0-32767]), // projectile's priority +movementPenalty (int, number of seconds) // enters WEAPON_STUN when the ki attack was being used +----- +The following sample testing torso ki attack animations are used with: +(ultimate_blast) WP_GRAPPLING_HOOK would be "beam", chargeAttack 1, chargeAutoFire 0, loopingAnim 0, noAttackAnim 0, priority 1, movementPenalty 0 +(finger_blast) WP_MACHINEGUN would be "hitscan", chargeAttack 0, chargeAutoFire 0, loopingAnim 1, noAttackAnim 0, priority 0, movementPenalty 0 +(ki_blast) WP_ROCKET_LAUNCHER would be "missile", chargeAttack 0, chargeAutoFire 0, loopingAnim 0, noAttackAnim 0, priority 0, movementPenalty 0 +(super_homing) WP_GRENADE_LAUNCHER would be "missile", chargeAttack 1, chargeAutoFire 0, loopingAnim 1, noAttackAnim 0, priority 0, movementPenalty 0 +(finger_beam) WP_RAILGUN would be "hitscan", chargeAttack 0, chargeAutoFire 0, loopingAnim 0, noAttackAnim 0, priority 0, movementPenalty 0 +(eyebeam) WP_LIGHTNING would be "hitscan", chargeAttack 0, chargeAutoFire 0, loopingAnim 0, noAttackAnim 0, priority 0, movementPenalty 0 +(homing_special) WP_PLASMAGUN would be "rdmissile", chargeAttack 1, chargeAutoFire 0, loopingAnim 0, noAttackAnim 0, priority 0, movementPenalty 0 +(aga) WP_SHOTGUN would be "forcefield", chargeAttack 1, chargeAutoFire 1, loopingAnim 1, noAttackAnim 0, priority 2, movementPenalty 2 +(blinding_flash) would be "forcefield", chargeAttack 1, chargeAutoFire 0, loopingAnim 0, noAttackAnim 1, priority 0, movementPenalty 0 +WP_BFG would be like super_homing, just throw the homing ki ball + +About "sbeam" attackType would be like a beam that, by holding down the attack key, +you direct it wherever you want by moving the cursor. +If you stop pressing the attack key, it explodes to the point where it arrived. +This attackType was originally left unfinished, +so there's a bug: after colliding the beam into something solid and +keep holding down the attack key, keeps muzzling and +doesn't shoot anything while the ki is wasted out of control. +================== +*/ +static void PM_KiAttackTorsoAnim( void ) { // BFP - Torso ki attack anims + if ( ( pm->cmd.buttons & BUTTON_ATTACK ) && !PM_IsInKiAttackState() ) { + switch( pm->ps->weapon ) { + case WP_ROCKET_LAUNCHER: { PM_AttackAnim( TORSO_ATTACK1_STRIKE, TORSO_ATTACK1_PREPARE ); break; } + case WP_GRENADE_LAUNCHER: { PM_AttackAnim( TORSO_ATTACK2_STRIKE, TORSO_ATTACK2_PREPARE ); break; } + case WP_RAILGUN: { PM_AttackAnim( TORSO_ATTACK3_STRIKE, TORSO_ATTACK3_PREPARE ); break; } + case WP_PLASMAGUN: { PM_AttackAnim( TORSO_ATTACK3_STRIKE, TORSO_ATTACK3_PREPARE ); break; } + case WP_SHOTGUN: + case WP_BFG: + case WP_GRAPPLING_HOOK: { PM_AttackAnim( TORSO_ATTACK4_STRIKE, TORSO_ATTACK4_PREPARE ); break; } + } + } else if ( PM_IsInKiAttackState() ) { + pm->cmd.buttons &= ~BUTTON_GESTURE; + switch( pm->ps->weapon ) { + default: + case WP_MACHINEGUN: { PM_AttackAnim( TORSO_ATTACK0_STRIKE, TORSO_ATTACK0_PREPARE ); break; } + case WP_ROCKET_LAUNCHER: { PM_AttackAnim( TORSO_ATTACK1_STRIKE, TORSO_ATTACK1_PREPARE ); break; } + case WP_GRENADE_LAUNCHER: { PM_AttackAnim( TORSO_ATTACK2_STRIKE, TORSO_ATTACK2_PREPARE ); break; } + case WP_PLASMAGUN: + case WP_RAILGUN: { PM_AttackAnim( TORSO_ATTACK3_STRIKE, TORSO_ATTACK3_PREPARE ); break; } + case WP_SHOTGUN: + case WP_BFG: + case WP_GRAPPLING_HOOK: { PM_AttackAnim( TORSO_ATTACK4_STRIKE, TORSO_ATTACK4_PREPARE ); break; } + } + } +} + +/* +================== +PM_TorsoStatusAnim +================== +*/ +static void PM_TorsoStatusAnim( int anim ) { // BFP - Torso status handling + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + if ( pm->ps->pm_flags & PMF_BLOCK ) PM_ContinueTorsoAnim( TORSO_BLOCK ); + else if ( ( pm->cmd.buttons & BUTTON_MELEE ) && !( pm->ps->pm_flags & PMF_MELEE ) ) PM_ContinueTorsoAnim( TORSO_MELEE_READY ); + else if ( pm->ps->pm_flags & PMF_MELEE ) PM_ContinueTorsoAnim( TORSO_MELEE_STRIKE ); + else if ( ( pm->cmd.buttons & BUTTON_ATTACK ) + || PM_IsInKiAttackState() ) PM_KiAttackTorsoAnim(); + else PM_ContinueTorsoAnim( anim ); +} + +/* +================== +PM_ForceJumpAnim +================== +*/ +static void PM_ForceJumpAnim( void ) { // BFP - Jump anim handling + ( pm->cmd.forwardmove >= 0 ) ? PM_ForceLegsAnim( LEGS_JUMP ) : PM_ForceLegsAnim( LEGS_JUMPB ); +} + +/* +================== +PM_ContinueFlyAnim +================== +*/ +static void PM_ContinueFlyAnim( void ) { // BFP - Continuous fly anim handling + if ( pm->ps->pm_flags & PMF_MELEE ) { return; } + if ( pm->cmd.forwardmove > 0 ) { PM_TorsoStatusAnim( TORSO_FLYA ); PM_ContinueLegsAnim( LEGS_FLYA ); } + else if ( pm->cmd.forwardmove < 0 ) { PM_TorsoStatusAnim( TORSO_FLYB ); PM_ContinueLegsAnim( LEGS_FLYB ); } + else { PM_TorsoStatusAnim( TORSO_STAND ); PM_ContinueLegsAnim( LEGS_FLYIDLE ); } +} + +/* +=========================== +PM_ContinueMeleeStrikeLegsAnim +=========================== +*/ +static void PM_ContinueMeleeStrikeLegsAnim( qboolean condition ) { // BFP - Melee strike legs anim handling + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + // keep moving the legs when the player is attacking to the target through melee + // if the condition variable isn't used leave using this value: 1 or qtrue + if ( ( condition ) && ( pm->ps->pm_flags & PMF_MELEE ) + && pm->ps->stats[STAT_HITSTUN_TIME] <= 0 && !( pm->ps->pm_flags & PMF_KI_CHARGE ) ) { PM_ContinueLegsAnim( LEGS_MELEE_STRIKE ); } +} + +/* +================== +PM_SlopesNeargroundAnim +================== +*/ +static void PM_SlopesNeargroundAnim( qboolean is_slope ) { // BFP - Animation handling on the slopes and when being near to the ground + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + + if ( pm->waterlevel > 0 ) { // not for water + return; + } + + // don't apply if it's already striking + if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_MELEE_STRIKE ) { + return; + } + + if ( is_slope ) { + if ( pm->ps->pm_flags & PMF_DUCKED ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + if ( pm->cmd.forwardmove < 0 + || ( pm->cmd.forwardmove > 0 + || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) ) { PM_ContinueLegsAnim( LEGS_WALKCR ); } + PM_TorsoStatusAnim( TORSO_STAND ); + return; + } + } else { + // if it's trying to crouch, then play jumping animation once + if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMP + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMPB ) { + return; + } + if ( pm->ps->pm_flags & PMF_DUCKED ) { + PM_ForceJumpAnim(); + return; + } + } + // if it's very near to the other entity and the melee strike is executed, continue playing the melee strike legs animation + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { PM_ContinueLegsAnim( LEGS_IDLE ); PM_ContinueMeleeStrikeLegsAnim( qtrue ); return; } + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + if ( pm->cmd.forwardmove < 0 ) { PM_ContinueLegsAnim( LEGS_BACK ); PM_TorsoStatusAnim( TORSO_STAND ); } + else if ( pm->cmd.forwardmove > 0 + || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { PM_ContinueLegsAnim( LEGS_RUN ); PM_TorsoStatusAnim( TORSO_RUN ); } + } else { + if ( pm->cmd.forwardmove < 0 ) { PM_ContinueLegsAnim( LEGS_BACK ); } + else if ( pm->cmd.forwardmove > 0 + || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { PM_ContinueLegsAnim( LEGS_WALK ); } + PM_TorsoStatusAnim( TORSO_STAND ); + } + PM_ContinueMeleeStrikeLegsAnim( qtrue ); +} /* ================== @@ -183,10 +545,14 @@ static void PM_Friction( void ) { speed = VectorLength(vec); if (speed < 1) { - drop = 0; vel[0] = 0; vel[1] = 0; // allow sinking underwater // FIXME: still have z friction underwater? + + // BFP - Brake when flying at that speed rate, otherwise the friction continues + if ( pm->ps->eFlags & EF_FLIGHT ) { + vel[2] = 0; + } return; } @@ -195,8 +561,9 @@ static void PM_Friction( void ) { // 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) ) { + if ( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) { control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control*pm_friction*pml.frametime; } @@ -204,13 +571,14 @@ static void PM_Friction( void ) { } // apply water friction even if just wading - if ( pm->waterlevel ) { + if ( pm->waterlevel + && !( pm->ps->eFlags & EF_FLIGHT ) && !( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) ) { // BFP - Don't apply on flight drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; } // apply flying friction // BFP - Flight - if ( pm->ps->pm_flags & PMF_FLYING ) { + if ( ( pm->ps->eFlags & EF_FLIGHT ) || ( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) ) { control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control*pm_flightfriction*pml.frametime; } @@ -232,6 +600,28 @@ static void PM_Friction( void ) { } +/* +================== +PM_KiBoostPowerlevelSpeed +================== +*/ +static float PM_KiBoostPowerlevelSpeed( void ) { // BFP - Powerlevel ki boost speed + int powerlevel = pm->ps->persistant[PERS_POWERLEVEL]; + return pm->ps->speed + ( powerlevel * 0.5 ); +} + + +/* +================ +PM_IsBeamFiring +================ +*/ +static qboolean PM_IsBeamFiring( void ) { + return ( pm->attackType == ATK_BEAM || pm->attackType == ATK_SBEAM ) + && pm->ps->weaponstate == WEAPON_ACTIVE; +} + + /* ============== PM_Accelerate @@ -292,6 +682,20 @@ static float PM_CmdScale( usercmd_t *cmd ) { int max; float total; float scale; + // BFP - Monster gamemode, player monster speed is faster + float speed = pm->ps->speed; + if ( pm->ps->eFlags & EF_MONSTER ) { + if ( !( pm->ps->eFlags & EF_FLIGHT ) ) { + speed *= 2; + } else { + if ( !( pm->ps->eFlags & EF_KI_BOOST ) + && !( pm->cmd.buttons & BUTTON_KI_USE ) ) { + speed *= 1.5; + } else { + speed *= 2; + } + } + } max = abs( cmd->forwardmove ); if ( abs( cmd->rightmove ) > max ) { @@ -306,7 +710,7 @@ static float PM_CmdScale( usercmd_t *cmd ) { total = sqrt( cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); - scale = (float)pm->ps->speed * max / ( 127.0 * total ); + scale = (float)speed * max / ( 127.0 * total ); return scale; } @@ -362,6 +766,12 @@ static qboolean PM_CheckJump( void ) { return qfalse; // don't allow jump until all buttons are up } + // BFP - With ki charge, the player can't jump. With hit stun, avoids jittering movements + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 + || ( pm->ps->pm_flags & PMF_KI_CHARGE ) ) { + return qfalse; + } + if ( pm->cmd.upmove < 10 ) { // not holding jump return qfalse; @@ -377,22 +787,141 @@ static qboolean PM_CheckJump( void ) { pml.groundPlane = qfalse; // jumping away pml.walking = qfalse; pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->pm_flags &= ~PMF_AIR_GRAVITY; // BFP - Air gravity pm->ps->groundEntityNum = ENTITYNUM_NONE; pm->ps->velocity[2] = JUMP_VELOCITY; - PM_AddEvent( EV_JUMP ); + if ( pm->ps->eFlags & EF_MONSTER ) { // BFP - Add more vertical jumping for player monster + pm->ps->velocity[2] += 250; + } + // BFP - Double jump velocity when using ki boost + if ( ( pm->ps->eFlags & EF_KI_BOOST ) + || ( pm->cmd.buttons & BUTTON_KI_USE ) ) { // BFP - Handle the ki boost button if it's being pressed, that avoids jittering movements + pm->ps->velocity[2] = 1085; + if ( pm->ps->eFlags & EF_MONSTER ) { // BFP - Add more vertical jumping for player monster + pm->ps->velocity[2] += 400; + } + } - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + // BFP - Jumping from slopes without backoffs + { + vec3_t point; + trace_t trace; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + if ( trace.plane.normal[2] < 1 ) { + float fmove, smove; + int i; + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 2 ; i++ ) { + pm->ps->velocity[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + } + + if ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove == 0 ) { + pm->ps->velocity[0] = 0; + pm->ps->velocity[1] = 0; + } } + PM_AddEvent( EV_JUMP ); + + // BFP - No PMF_BACKWARDS_JUMP handling (code removed) + PM_ForceJumpAnim(); + + PM_TorsoStatusAnim( TORSO_STAND ); + return qtrue; } +/* +=================== +PM_Drifting + +Drifting movements +=================== +*/ +static void PM_Drifting( void ) { // BFP - Drifting + vec3_t drift; + float forwardSpeed, rightSpeed, driftFactor = 0.0003; + + if ( pm->ps->pm_type == PM_DEAD || pm->ps->pm_type == PM_SPECTATOR + || ( pm->ps->pm_flags & PMF_RESPAWNED ) + // handling for underwater + || ( pm->waterlevel && pm->ps->velocity[0] <= 0 && pm->ps->velocity[1] <= 0 ) ) { + return; + } + + forwardSpeed = -DotProduct( pm->ps->velocity, pml.forward ); + rightSpeed = DotProduct( pm->ps->velocity, pml.right ); + + // apply directional drift when keys are released + if ( !pm->cmd.rightmove && Q_fabs( rightSpeed ) > 0.0f + && pm->ps->velocity[2] < 0 ) { + driftFactor = 0.001; + VectorScale( pml.up, driftFactor * Q_fabs( rightSpeed ), drift ); + VectorAdd( pm->ps->velocity, drift, pm->ps->velocity ); + } + + if ( !pm->cmd.forwardmove ) { + // drift a bit more when slowing down + if ( Q_fabs( forwardSpeed ) < 100.0f ) { + driftFactor = 0.008; + } + if ( forwardSpeed > 0 ) { // left + VectorScale( pml.right, driftFactor * Q_fabs( forwardSpeed ) * pml.right[1], drift ); + } else { // right + VectorScale( pml.right, -driftFactor * Q_fabs( forwardSpeed ) * pml.right[1], drift ); + } + VectorAdd( pm->ps->velocity, drift, pm->ps->velocity ); + } +} + +/* +============= +PM_CheckWaterSpot +============= +*/ +static qboolean PM_CheckWaterSpot( vec3_t direction, vec3_t spot, int horizontalVel, int verticalVel ) { // BFP - Check spot to jump off water + int cont = 0; + // BFP - Monster gamemode, use the measures to jump correctly near to the spot + float spotDir = 1, spotUnits = 1; + if ( pm->ps->eFlags & EF_MONSTER ) { + spotDir = 2.5; + spotUnits = 5.625; + verticalVel *= spotDir; + horizontalVel *= spotDir; + } + + VectorMA ( pm->ps->origin, 30 * spotDir, direction, spot ); + spot[2] += 4; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + if ( cont & CONTENTS_SOLID ) { + spot[2] += 16 * spotUnits; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + if ( !cont ) { + VectorScale( pml.forward, horizontalVel, pm->ps->velocity ); + pm->ps->velocity[2] = verticalVel; + PM_ForceJumpAnim(); + return qtrue; + } + } + return qfalse; +} + /* ============= PM_CheckWaterJump @@ -400,8 +929,9 @@ PM_CheckWaterJump */ static qboolean PM_CheckWaterJump( void ) { vec3_t spot; - int cont; - vec3_t flatforward; + // BFP - Apply for backwards, left and right too, Q3 doesn't have that + vec3_t flatforward, flatbackward, flatleft, flatright; + const int WATER_JUMP_HORIZONTAL_VELOCITY = 200, WATER_JUMP_VERTICAL_VELOCITY = 300; if (pm->ps->pm_time) { return qfalse; @@ -417,27 +947,55 @@ static qboolean PM_CheckWaterJump( void ) { 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) ) { + // backward direction + flatbackward[0] = -pml.forward[0]; + flatbackward[1] = -pml.forward[1]; + flatbackward[2] = 0; + VectorNormalize( flatbackward ); + + // left direction + flatleft[0] = -pml.right[0]; + flatleft[1] = -pml.right[1]; + flatleft[2] = 0; + VectorNormalize( flatleft ); + + // right direction + flatright[0] = pml.right[0]; + flatright[1] = pml.right[1]; + flatright[2] = 0; + VectorNormalize( flatright ); + + // BFP - Don't auto-jump forward/backward + if ( pm->cmd.forwardmove == 0 ) { return qfalse; } - spot[2] += 16; - cont = pm->pointcontents (spot, pm->ps->clientNum ); - if ( cont ) { + // check forward + if ( PM_CheckWaterSpot( flatforward, spot, WATER_JUMP_HORIZONTAL_VELOCITY, WATER_JUMP_VERTICAL_VELOCITY ) ) { + return qtrue; + } + + // check backward + if ( PM_CheckWaterSpot( flatbackward, spot, -WATER_JUMP_HORIZONTAL_VELOCITY, WATER_JUMP_VERTICAL_VELOCITY ) ) { + return qtrue; + } + + // BFP - Don't auto-jump left/right + if ( pm->cmd.rightmove == 0 ) { return qfalse; } - // jump out of water - VectorScale (pml.forward, 200, pm->ps->velocity); - pm->ps->velocity[2] = 350; + // check left + if ( PM_CheckWaterSpot( flatleft, spot, -WATER_JUMP_HORIZONTAL_VELOCITY, WATER_JUMP_VERTICAL_VELOCITY ) ) { + return qtrue; + } - pm->ps->pm_flags |= PMF_TIME_WATERJUMP; - pm->ps->pm_time = 2000; + // check right + if ( PM_CheckWaterSpot( flatright, spot, WATER_JUMP_HORIZONTAL_VELOCITY, WATER_JUMP_VERTICAL_VELOCITY ) ) { + return qtrue; + } - return qtrue; + return qfalse; } //============================================================================ @@ -456,6 +1014,7 @@ static void PM_WaterJumpMove( void ) { 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; @@ -477,17 +1036,42 @@ static void PM_WaterMove( void ) { float scale; float vel; + // BFP - Avoid adding friction under water while flying + if ( pm->ps->eFlags & EF_FLIGHT ) { + return; + } + + // BFP - With ki charge, the player can't move, even up or down + if ( pm->ps->pm_flags & PMF_KI_CHARGE ) { + pm->cmd.upmove = 0; + } + if ( PM_CheckWaterJump() ) { PM_WaterJumpMove(); return; } + + // BFP - Underwater animation handling, uses flying animation in that case + if ( pm->waterlevel > 2 ) { + PM_ContinueFlyAnim(); + // keep charging animation, otherwise looks jerky + if ( ( pm->cmd.buttons & BUTTON_KI_CHARGE ) + && !( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) { // avoid forcing animations on transformation phase + PM_ContinueTorsoAnim( TORSO_CHARGE ); + PM_ContinueLegsAnim( LEGS_CHARGE ); + } + } + + // BFP - Melee strike legs animation + PM_ContinueMeleeStrikeLegsAnim( qtrue ); + #if 0 // jump = head for surface if ( pm->cmd.upmove >= 10 ) { if (pm->ps->velocity[2] > -300) { - if ( pm->watertype == CONTENTS_WATER ) { + if ( pm->watertype & CONTENTS_WATER ) { pm->ps->velocity[2] = 100; - } else if (pm->watertype == CONTENTS_SLIME) { + } else if ( pm->watertype & CONTENTS_SLIME ) { pm->ps->velocity[2] = 80; } else { pm->ps->velocity[2] = 50; @@ -498,6 +1082,12 @@ static void PM_WaterMove( void ) { PM_Friction (); scale = PM_CmdScale( &pm->cmd ); + + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + scale = 0; + } + // // user intentions // @@ -506,18 +1096,53 @@ static void PM_WaterMove( void ) { wishvel[1] = 0; wishvel[2] = -60; // sink towards bottom } else { - for (i=0 ; i<3 ; i++) + for (i=0 ; i<3 ; i++) { wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + // BFP - Avoid going up, keep sinking + if ( i == 2 + && ( ( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE + && ( pm->ps->eFlags & EF_FIRING ) ) + || pm->ps->weaponstate == WEAPON_STUN + || pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) ) { + wishvel[2] = 0; + } + } + wishvel[2] += scale * pm->cmd.upmove; } + // BFP - Reduces speed when charging ki + if ( ( ( pm->ps->pm_flags & PMF_KI_CHARGE ) || ( pm->cmd.buttons & BUTTON_KI_CHARGE ) ) + && ( !( pm->ps->eFlags & EF_KI_BOOST ) && !( pm->cmd.buttons & BUTTON_KI_USE ) ) + && pm->ps->stats[STAT_HITSTUN_TIME] <= 0 ) { + wishvel[2] *= 0.15; + } + + // BFP - Sink on stunned status + if ( ( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE + && ( pm->ps->eFlags & EF_FIRING ) ) + || pm->ps->weaponstate == WEAPON_STUN + || pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) { + wishvel[2] -= pm->ps->gravity * pml.frametime; + PM_SlideMove( qtrue ); + } + VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); +// BFP - No water speed slowness +#if 0 if ( wishspeed > pm->ps->speed * pm_swimScale ) { wishspeed = pm->ps->speed * pm_swimScale; } +#endif + + if ( !PM_IsBeamFiring() // BFP - Don't increase speed when beam firing + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) + && ( pm->cmd.forwardmove != 0 || pm->cmd.rightmove != 0 || pm->cmd.upmove != 0 ) ) { + wishspeed += PM_KiBoostPowerlevelSpeed(); + } PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); @@ -533,6 +1158,52 @@ static void PM_WaterMove( void ) { } PM_SlideMove( qfalse ); + + PM_Drifting(); // BFP - Drifting +} + +/* +=================== +PM_FlyTiltView + +Fly tilt view +=================== +*/ +static void PM_FlyTiltView( void ) { // BFP - Fly tilt + static float currentRollAngle = 0; + int targetRollAngle = 0; + float rollStep = 0.2; + + if ( ( pm->ps->eFlags & EF_FLIGHT ) + && !( pm->ps->pm_flags & PMF_BLOCK ) + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) ) { + // determine the target roll angle based on rightmove + if ( pm->cmd.rightmove > 0 ) { + targetRollAngle = 20; + } else if ( pm->cmd.rightmove < 0 ) { + targetRollAngle = -20; + } + } + + // if going back to the main angle, increase a bit the roll step + if ( targetRollAngle == 0 ) { + rollStep = 0.375; + } + + // apply the fixed step for a more direct roll change + if ( currentRollAngle < targetRollAngle ) { + currentRollAngle += rollStep; + if ( currentRollAngle > targetRollAngle ) { + currentRollAngle = targetRollAngle; + } + } else if ( currentRollAngle > targetRollAngle ) { + currentRollAngle -= rollStep; + if ( currentRollAngle < targetRollAngle ) { + currentRollAngle = targetRollAngle; + } + } + + pm->ps->viewangles[ROLL] = currentRollAngle; } /* @@ -552,27 +1223,82 @@ static void PM_FlyMove( void ) { // normal slowdown PM_Friction (); + // BFP - With ki charge, the player can't move, even up or down + if ( pm->ps->pm_flags & PMF_KI_CHARGE ) { + pm->cmd.upmove = 0; + } + + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + scale = PM_CmdScale( &pm->cmd ); // // user intentions // if ( !scale ) { - wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = 0; + VectorClear( wishvel ); } else { for ( i = 0; i < 3; i++ ) { - wishvel[i] = scale * pml.forward[i] * pm->cmd.forwardmove + scale * pml.right[i] * pm->cmd.rightmove + scale * pml.up[i] * pm->cmd.upmove; // BFP - (+ scale * pml.up[i] * pm->cmd.upmove) used when flying and moving upside/downside instead according the ground + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove + scale * pml.up[i]*pm->cmd.upmove; + // BFP - Keep moving up if forward/backward, left/right and up directional keys are pressed + if ( pm->cmd.forwardmove != 0 && pm->cmd.rightmove != 0 && pm->cmd.upmove != 0 ) { + wishvel[i] += 12; + } + } + + // BFP - Going up/down a bit down when moving left/right depending how the player looks + if ( !( pm->ps->pm_flags & PMF_BLOCK ) + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) + && pm->ps->pm_type != PM_SPECTATOR ) { + + // handle downward push when moving left/right without upmove + if ( pm->cmd.rightmove != 0 && pm->cmd.upmove <= 0 ) { + vec3_t downPush; + int pushAmount = 100; + + if ( ( pm->attackType == ATK_BEAM || pm->attackType == ATK_SBEAM ) && pm->ps->weaponstate == WEAPON_ACTIVE ) { + pushAmount = 80; + } + + VectorScale( pml.up, -pushAmount, downPush ); + VectorAdd( wishvel, downPush, wishvel ); + } } - // wishvel[2] += scale * pm->cmd.upmove; // BFP - disabled to work the wished velocity } VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; // add speed + + if ( !( pm->ps->pm_flags & PMF_BLOCK ) // BFP - Don't increase the speed when blocking + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) + && !PM_IsBeamFiring() ) { // BFP - Don't increase speed when beam firing + float factor = 2.0f + ( (float)pm->ps->persistant[PERS_POWERLEVEL] * 0.001 ); + wishspeed += PM_KiBoostPowerlevelSpeed() * factor; + } + +// BFP - Debugging view and origin +#if 0 + Com_Printf( "----------------------------------------------------------------------------------------------------------\n" ); + for ( i = 0; i < 3; i++ ) { + Com_Printf( "^1pm->ps->origin[%d]: %f - ", i, pm->ps->origin[i] ); + Com_Printf( "^3pml.forward[%d]: %f - ", i, pml.forward[i] ); + Com_Printf( "^6pml.right[%d]: %f - ", i, pml.right[i] ); + Com_Printf( "^4pml.up[%d]: %f\n", i, pml.up[i] ); + } +#endif + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); PM_StepSlideMove( qfalse ); + + // BFP - Drifting + if ( pm->ps->pm_time <= 0 ) { + PM_Drifting(); + } } @@ -637,8 +1363,38 @@ static void PM_AirMove( void ) { else PM_SlideMove ( qtrue ); #endif + PM_TorsoStatusAnim( TORSO_STAND ); + + // BFP - Reduces speed when charging ki + if ( ( ( pm->ps->pm_flags & PMF_KI_CHARGE ) || ( pm->cmd.buttons & BUTTON_KI_CHARGE ) ) + && ( !( pm->ps->eFlags & EF_KI_BOOST ) && !( pm->cmd.buttons & BUTTON_KI_USE ) ) + && pm->ps->stats[STAT_HITSTUN_TIME] <= 0 ) { + if ( pm->ps->velocity[2] > -10 ) { + pm->ps->velocity[2] *= 0.9; + } + wishvel[2] = -10; + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); + PM_SlideMove( qfalse ); + pm->ps->velocity[2] *= 0.99; + return; + } PM_StepSlideMove ( qtrue ); + + // BFP - Handle gravity, make the player heavier + if ( !( pm->ps->pm_flags & PMF_AIR_GRAVITY ) ) { + // BFP - Adjust step slide movement for player monster + if ( !( pm->ps->eFlags & EF_MONSTER ) ) { + PM_SlideMove ( qtrue ); + } else { + PM_StepSlideMove ( qtrue ); + } + return; + } + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; } // BFP - no hook @@ -687,6 +1443,11 @@ static void PM_WalkMove( void ) { float accelerate; float vel; + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { // begin swimming PM_WaterMove(); @@ -743,6 +1504,8 @@ static void PM_WalkMove( void ) { } } +// BFP - No water speed slowness +#if 0 // clamp the speed lower if wading or walking on the bottom if ( pm->waterlevel ) { float waterScale; @@ -753,10 +1516,20 @@ static void PM_WalkMove( void ) { wishspeed = pm->ps->speed * waterScale; } } +#endif + + if ( !PM_IsBeamFiring() // BFP - Don't increase speed when beam firing + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) ) { + if ( pm->ps->pm_flags & PMF_DUCKED ) { // ducking uses 30% of speed + wishspeed += ( 1.0 - pm_duckScale ) * ( PM_KiBoostPowerlevelSpeed() * 0.3 ); + } else { + wishspeed += PM_KiBoostPowerlevelSpeed(); + } + } // 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 ) { + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || ( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) { accelerate = pm_airaccelerate; } else { accelerate = pm_accelerate; @@ -767,13 +1540,31 @@ static void PM_WalkMove( void ) { //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 ) { + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || ( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) { pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; } else { // don't reset the z velocity for slopes // pm->ps->velocity[2] = 0; } + // BFP - Sink on stunned status + if ( ( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE + && ( pm->ps->eFlags & EF_FIRING ) ) + || pm->ps->weaponstate == WEAPON_STUN + || pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + + // slide along the ground plane + PM_ClipVelocity ( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + PM_SlideMove ( qtrue ); + PM_StepSlideMove ( qtrue ); + + PM_Drifting(); // BFP - Drifting + return; + } + vel = VectorLength(pm->ps->velocity); // slide along the ground plane @@ -793,6 +1584,7 @@ static void PM_WalkMove( void ) { //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + PM_Drifting(); // BFP - Drifting } @@ -917,15 +1709,16 @@ static void PM_CrashLand( void ) { // decide which landing animation to use // BFP - Non-existant animations - /* +#if 0 if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { PM_ForceLegsAnim( LEGS_LANDB ); } else { PM_ForceLegsAnim( LEGS_LAND ); } - */ +#endif - pm->ps->legsTimer = TIMER_LAND; + // BFP - No timer land on the legs + // pm->ps->legsTimer = TIMER_LAND; // calculate the exact velocity on landing dist = pm->ps->origin[2] - pml.previous_origin[2]; @@ -966,20 +1759,20 @@ static void PM_CrashLand( void ) { 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 ( delta > 180 ) { // BFP - Before Q3 default value (60), so the fall in BFP is further + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) + && pm->ps->pm_type != PM_DEAD ) { // BFP - Don't play on dead status + if ( delta > 180 ) { // BFP - Before Q3 default value (60), the far fall in BFP is deeper PM_AddEvent( EV_FALL_FAR ); - } /* else if ( delta > 40 ) { // BFP - There's no medium fall on BFP + } else if ( delta > 60 ) { // BFP - Before Q3 default value (40), the far medium in BFP is a bit deeper // this is a pain grunt, so don't play it if dead if ( pm->ps->stats[STAT_HEALTH] > 0 ) { PM_AddEvent( EV_FALL_MEDIUM ); } - } */ else if ( delta > 7 ) { + } else if ( delta > 30 ) { // BFP - Fall at that velocity PM_AddEvent( EV_FALL_SHORT ); } else { PM_AddEvent( PM_FootstepForSurface() ); @@ -995,16 +1788,31 @@ static void PM_CrashLand( void ) { PM_CheckStuck ============= */ -/* -void PM_CheckStuck(void) { +#if 0 /* BFP - Disabled, since that doesn't make sense */ +static void PM_CheckStuck(void) { + // BFP - NOTE: Curiously and originally, BFP uses this function to animate when the player is stuck, + // that can be tested when the player is pretty near to the other player + // or being stuck in the same origin as the other player, specially outside water. + // It has been implemented when melee animations were being used trace_t trace; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); if (trace.allsolid) { //int shit = qtrue; + + // BFP - Handle the animations when being stuck! (Only outside water) + if ( pm->waterlevel < 1 ) { + if ( pm->cmd.forwardmove < 0 ) { + PM_ContinueLegsAnim( LEGS_JUMPB ); + } else { + PM_ContinueLegsAnim( LEGS_JUMP ); + } + } + // BFP - Melee strike legs animation + PM_ContinueMeleeStrikeLegsAnim( qtrue ); } } -*/ +#endif /* ============= @@ -1041,11 +1849,60 @@ static int PM_CorrectAllSolid( trace_t *trace ) { } } - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + +/* +============= +PM_ControlJumpOnGround +============= +*/ +static void PM_ControlJumpOnGround( void ) { // BFP - A control to handle user movement intentions when jumping off the ground + if ( pm->ps->weaponstate != WEAPON_STUN + && pm->ps->groundEntityNum != ENTITYNUM_NONE + && !( pm->ps->eFlags & EF_FLIGHT ) + && ( pm->cmd.upmove > 0 || ( pm->ps->pm_flags & PMF_JUMP_HELD ) ) + && ( pm->cmd.forwardmove > 0 || pm->cmd.forwardmove < 0 + || pm->cmd.rightmove > 0 || pm->cmd.rightmove < 0 ) ) { + float fmove, smove; + float vel; + int i; + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 2 ; i++ ) { + pm->ps->velocity[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + if ( !PM_IsBeamFiring() // BFP - Don't increase speed when beam firing + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) ) { + pm->ps->velocity[0] *= 10; + pm->ps->velocity[1] *= 10; + } else { // BFP - Add a bit of forward/backward speed + pm->ps->velocity[0] *= 3.5; + pm->ps->velocity[1] *= 3.5; + } + vel = VectorLength( pm->ps->velocity ); + if ( vel > 640 ) { // keep maximum speed + vel = 640; + } + + // slide along the ground plane + PM_ClipVelocity ( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); - return qfalse; + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + } } @@ -1073,19 +1930,12 @@ static void PM_GroundTraceMissed( void ) { pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); if ( trace.fraction == 1.0 ) { - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } + // BFP - No PMF_BACKWARDS_JUMP handling (code removed) + PM_ForceJumpAnim(); } } pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; } @@ -1105,8 +1955,8 @@ static void PM_GroundTrace( void ) { pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); pml.groundTrace = trace; - // BFP - Flight, if flying, do nothing when touching the ground - if ( pm->ps->pm_flags & PMF_FLYING ) { + // BFP - No ground trace handling in the water + if ( pm->waterlevel > 1 ) { return; } @@ -1129,14 +1979,6 @@ static void PM_GroundTrace( void ) { if ( pm->debugLevel ) { Com_Printf("%i:kickoff\n", c_pmove); } - // go into jump animation - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qfalse; @@ -1154,20 +1996,63 @@ static void PM_GroundTrace( void ) { pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qtrue; pml.walking = qfalse; + + // BFP - If flying, prevent from doing a jumping action on slopes + if ( pm->ps->eFlags & EF_FLIGHT ) { + return; + } + + // BFP - Handle if the player is trying to jump and/or do another movements + // when stepping the steep slopes + if ( PM_CheckJump () ) { + // BFP - Handle jumping and changing the direction + PM_ControlJumpOnGround(); + if ( ( pm->cmd.upmove > 0 || ( pm->ps->pm_flags & PMF_JUMP_HELD ) ) + && !PM_IsBeamFiring() // BFP - Don't increase speed when beam firing + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) ) { + pm->ps->velocity[0] *= 5; + pm->ps->velocity[1] *= 5; + } + // jumped away + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + + PM_SlopesNeargroundAnim( 1 ); return; } + // BFP - NOTE: Originally, BFP doesn't stop "groundtracing" until here when the player is flying + // BFP - If flying, prevent from doing a jumping action on flat ground + if ( pm->ps->eFlags & EF_FLIGHT ) { + // BFP - To stick to the movers if the player is near to them + pm->ps->groundEntityNum = trace.entityNum; + PM_AddTouchEnt( trace.entityNum ); + return; + } + + pm->ps->pm_flags |= PMF_AIR_GRAVITY; // BFP - Air gravity + pml.groundPlane = qtrue; pml.walking = qtrue; + // BFP - No handling PMF_TIME_WATERJUMP +#if 0 // 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_flags &= ~PMF_TIME_WATERJUMP; // BFP: before: ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); pm->ps->pm_time = 0; } +#endif - if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + // BFP - Handle when the player isn't flying + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + && !( pm->ps->eFlags & EF_FLIGHT ) ) { // just hit the ground if ( pm->debugLevel ) { Com_Printf("%i:Land\n", c_pmove); @@ -1175,19 +2060,31 @@ static void PM_GroundTrace( void ) { PM_CrashLand(); + // BFP - PMF_TIME_LAND doesn't exist and it doesn't have any handle checks +#if 0 // 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; } +#endif } - if ( !( pm->ps->pm_flags & PMF_FLYING ) ) // BFP - Flight - pm->ps->groundEntityNum = trace.entityNum; + pm->ps->groundEntityNum = trace.entityNum; + + // BFP - Avoid abnormal speed (and strafe - or defrag) + if ( !( pm->ps->pm_flags & PMF_JUMP_HELD ) ) { // don't use the jump key for that + PM_ControlJumpOnGround(); + } // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; + // pm->ps->velocity[2] = 0; + + // BFP - Avoid jumping unintentionally when that happens + if ( trace.plane.normal[2] == 1.0 ) { + pm->ps->velocity[2] = 0; + } PM_AddTouchEnt( trace.entityNum ); } @@ -1246,22 +2143,6 @@ static void PM_CheckDuck (void) { trace_t trace; - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { - // invulnerability sphere has a 42 units radius - VectorSet( pm->mins, -42, -42, -42 ); - VectorSet( pm->maxs, 42, 42, 42 ); - } - else { - VectorSet( pm->mins, -15, -15, MINS_Z ); - VectorSet( pm->maxs, 15, 15, 16 ); - } - pm->ps->pm_flags |= PMF_DUCKED; - // pm->ps->viewheight = CROUCH_VIEWHEIGHT; // BFP - don't look a bit down while pressing down - return; - } - pm->ps->pm_flags &= ~PMF_INVULEXPAND; - pm->mins[0] = -15; pm->mins[1] = -15; @@ -1296,13 +2177,24 @@ static void PM_CheckDuck (void) if (pm->ps->pm_flags & PMF_DUCKED) { pm->maxs[2] = 16; - // pm->ps->viewheight = CROUCH_VIEWHEIGHT; // BFP - don't look a bit down while pressing down + pm->ps->viewheight = CROUCH_VIEWHEIGHT; } else { pm->maxs[2] = 32; pm->ps->viewheight = DEFAULT_VIEWHEIGHT; } + + // BFP - Monster gamemode, player monster bounding box sizes + if ( pm->ps->eFlags & EF_MONSTER ) { + pm->mins[0] = -37.5; // -15 * 2.5 + pm->mins[1] = -37.5; // -15 * 2.5 + pm->mins[2] = -135; // -24(MINS_Z) * 5.625 + + pm->maxs[0] = 37.5; // 15 * 2.5 + pm->maxs[1] = 37.5; // 15 * 2.5 + pm->maxs[2] *= 5.625; + } } @@ -1316,10 +2208,47 @@ PM_Footsteps =============== */ static void PM_Footsteps( void ) { - float bobmove; + float bobmove = 0.0f; int old; qboolean footstep; + // BFP - Hit stun and ultimate tier + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 || ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) { + return; + } + + // BFP - Ki explosion wave state + if ( ( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE + && ( pm->ps->eFlags & EF_FIRING ) ) ) { + PM_ContinueLegsAnim( LEGS_IDLE ); + return; + } + + // BFP - Ki explosion wave stun state + if ( pm->ps->weaponstate == WEAPON_STUN ) { + PM_ContinueTorsoAnim( TORSO_STUN ); + PM_ContinueLegsAnim( LEGS_IDLE ); + return; + } + + // BFP - Avoid when charging + if ( pm->ps->pm_flags & PMF_KI_CHARGE ) { + pm->ps->eFlags &= ~EF_FIRING; // don't display shooting effects + return; + } + + // BFP - Handle torso melee animation + if ( ( pm->cmd.buttons & BUTTON_MELEE ) || ( pm->ps->pm_flags & PMF_MELEE ) || pm->meleeHit ) { + PM_TorsoStatusAnim( TORSO_MELEE_READY ); + } + + // BFP - Avoid when flying (for melee strike animation, that's applied) + if ( pm->ps->eFlags & EF_FLIGHT ) { + // BFP - Melee strike legs animation, don't apply if it's playing the starting jump animation in the flight status + PM_ContinueMeleeStrikeLegsAnim( pm->ps->pm_time <= 0 ); + return; + } + // // calculate speed and cycle to be used for // all cyclic walking effects @@ -1328,14 +2257,25 @@ static void PM_Footsteps( void ) { + pm->ps->velocity[1] * pm->ps->velocity[1] ); if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { - - // if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - // PM_ContinueLegsAnim( LEGS_IDLECR ); - // } - // airborne leaves position in cycle intact, but doesn't advance - if ( pm->waterlevel > 1 ) { - PM_ContinueLegsAnim( LEGS_SWIM ); + // BFP - PM_CheckStuck has been moved here, Q3 and the rest of mods hadn't used this. Currently disabled, since that doesn't make sense + // PM_CheckStuck(); + + // BFP - Underwater animation handling, uses flying animation in that case + // also keep the torso + if ( pm->waterlevel > 0 ) { // BFP - Avoid bad animations when jumping off water + if ( pm->cmd.upmove == 0 ) { + PM_ContinueFlyAnim(); + } + if ( pm->waterlevel <= 1 && pm->cmd.upmove > 0 ) { + PM_ForceJumpAnim(); + } + } else { + // BFP - Keep the torso when using a ki attack even after charged, avoid when melee is being used + if ( !( pm->cmd.buttons & BUTTON_MELEE ) ) { + PM_KiAttackTorsoAnim(); + } } + return; } @@ -1343,12 +2283,20 @@ static void PM_Footsteps( void ) { if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { if ( pm->xyspeed < 5 ) { pm->ps->bobCycle = 0; // start at beginning of cycle again + if ( pm->ps->pm_flags & PMF_DUCKED ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + } else if ( !( pm->ps->pm_flags & PMF_KI_CHARGE ) ) { + PM_ContinueLegsAnim( LEGS_IDLE ); + } + } else { // BFP - Handle the legs while it isn't doing nothing if ( pm->ps->pm_flags & PMF_DUCKED ) { PM_ContinueLegsAnim( LEGS_IDLECR ); } else { PM_ContinueLegsAnim( LEGS_IDLE ); } } + // BFP - Melee strike legs animation + PM_ContinueMeleeStrikeLegsAnim( qtrue ); return; } @@ -1357,10 +2305,14 @@ static void PM_Footsteps( void ) { if ( pm->ps->pm_flags & PMF_DUCKED ) { bobmove = 0.5; // ducked characters bob much faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_WALKCR ); // BFP - before LEGS_BACKCR - } else { - PM_ContinueLegsAnim( LEGS_WALKCR ); + // BFP - Replaced PMF_BACKWARDS_RUN handling + if ( pml.groundTrace.contents & MASK_PLAYERSOLID ) { + if ( pm->cmd.forwardmove < 0 ) { + PM_ContinueLegsAnim( LEGS_WALKCR ); // BFP - before LEGS_BACKCR + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + PM_ContinueLegsAnim( LEGS_WALKCR ); + } + PM_TorsoStatusAnim( TORSO_STAND ); // BFP - Keep the torso } // ducked characters never play footsteps /* @@ -1374,25 +2326,32 @@ static void PM_Footsteps( void ) { PM_ContinueLegsAnim( LEGS_BACK ); */ } else { - if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) && ( pml.groundTrace.contents & MASK_PLAYERSOLID ) ) { bobmove = 0.4f; // faster speeds bob faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + // BFP - Replaced PMF_BACKWARDS_RUN handling + if ( pm->cmd.forwardmove < 0 ) { PM_ContinueLegsAnim( LEGS_BACK ); - } else { + PM_TorsoStatusAnim( TORSO_STAND ); // BFP - Keep the torso + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { PM_ContinueLegsAnim( LEGS_RUN ); - PM_ContinueTorsoAnim( TORSO_RUN ); // BFP - Torso run animation + PM_TorsoStatusAnim( TORSO_RUN ); // BFP - Keep the torso } footstep = qtrue; - } else { + } else if ( pml.groundTrace.contents & MASK_PLAYERSOLID ) { bobmove = 0.3f; // walking bobs slow - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + // BFP - Replaced PMF_BACKWARDS_RUN handling + if ( pm->cmd.forwardmove < 0 ) { PM_ContinueLegsAnim( LEGS_BACK ); - } else { + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { PM_ContinueLegsAnim( LEGS_WALK ); } + PM_TorsoStatusAnim( TORSO_STAND ); // BFP - Keep the torso } } + // BFP - Melee strike legs animation + PM_ContinueMeleeStrikeLegsAnim( qtrue ); + // check for footstep / splash sounds old = pm->ps->bobCycle; pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; @@ -1425,18 +2384,48 @@ Generate sound events for entering and leaving water ============== */ static void PM_WaterEvents( void ) { // FIXME? + vec3_t point; + int cont; + + // BFP - Jumping off water surface, handle sound event for footsplash + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + MINS_Z + 6; + cont = pm->pointcontents( point, pm->ps->clientNum ); + // // if just entered a water volume, play a sound // if (!pml.previous_waterlevel && pm->waterlevel) { - PM_AddEvent( EV_WATER_TOUCH ); + if ( !( cont & MASK_WATER ) + && !( pm->ps->eFlags & EF_FLIGHT ) + && pm->cmd.upmove > 0 ) { + PM_AddEvent( EV_FOOTSPLASH ); // BFP - Play a different and smooth sound + return; + } else { + 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 ); + if ( !( cont & MASK_WATER ) + && !( pm->ps->eFlags & EF_FLIGHT ) + && pm->cmd.upmove > 0 ) { + PM_AddEvent( EV_FOOTSPLASH ); // BFP - Play a different and smooth sound + } else { + PM_AddEvent( EV_WATER_LEAVE ); + } + if ( !( pm->ps->eFlags & EF_FLIGHT ) + && !( pm->cmd.buttons & BUTTON_KI_CHARGE ) + && !( pm->ps->pm_flags & PMF_KI_CHARGE ) + && !( pm->ps->pm_flags & PMF_MELEE ) + && !( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) + && pm->ps->stats[STAT_HITSTUN_TIME] <= 0 ) { + PM_ForceJumpAnim(); // BFP - Keep legs animation + } } // @@ -1475,8 +2464,10 @@ static void PM_BeginWeaponChange( int weapon ) { PM_AddEvent( EV_CHANGE_WEAPON ); pm->ps->weaponstate = WEAPON_DROPPING; - pm->ps->weaponTime += 200; - PM_StartTorsoAnim( TORSO_ATTACK0_PREPARE ); // BFP - before TORSO_DROP + // BFP - Don't add weaponTime when changing ki attack + // pm->ps->weaponTime += 200; + // BFP - Non-existant animation + // PM_StartTorsoAnim( TORSO_DROP ); } @@ -1499,8 +2490,10 @@ static void PM_FinishWeaponChange( void ) { pm->ps->weapon = weapon; pm->ps->weaponstate = WEAPON_RAISING; - pm->ps->weaponTime += 250; - PM_StartTorsoAnim( TORSO_ATTACK0_PREPARE ); // BFP - before TORSO_RAISE + // BFP - Don't add weaponTime when changing ki attack + // pm->ps->weaponTime += 250; + // BFP - Non-existant animation + // PM_StartTorsoAnim( TORSO_RAISE ); } @@ -1511,11 +2504,105 @@ PM_TorsoAnimation ============== */ static void PM_TorsoAnimation( void ) { + // BFP - NOTE: That function could be called as "PM_NearGround", + // here is tracing something similar to PM_GroundTraceMissed + trace_t trace; + vec3_t point; + int cont; + // BFP - Handle the jump velocity after touching water surface, + // the maximum speed is one-third of the powerlevel + float vel, maxSpeed = 400 + ( pm->ps->persistant[PERS_POWERLEVEL] * 0.25 ); + + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + + // BFP - Jumping off water surface + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + MINS_Z + 6; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if ( ( cont & MASK_WATER ) + && pm->waterlevel <= 2 + && !( pm->ps->eFlags & EF_FLIGHT ) + && pm->cmd.upmove > 0 ) { + pm->ps->velocity[2] = 200; + // Control the player depending their moves + if ( pm->cmd.forwardmove > 0 || pm->cmd.forwardmove < 0 + || pm->cmd.rightmove > 0 || pm->cmd.rightmove < 0 ) { + float fmove, smove; + int i; + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 2 ; i++ ) { + pm->ps->velocity[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + if ( !PM_IsBeamFiring() // BFP - Don't increase speed when beam firing + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) ) { + pm->ps->velocity[0] *= 10; + pm->ps->velocity[1] *= 10; + } + } + // increase jumping speed using ki boost while not moving directionally + if ( !PM_IsBeamFiring() // BFP - Don't increase speed when beam firing + && ( ( pm->ps->eFlags & EF_KI_BOOST ) || ( pm->cmd.buttons & BUTTON_KI_USE ) ) + && !( pm->cmd.forwardmove > 0 || pm->cmd.forwardmove < 0 + || pm->cmd.rightmove > 0 || pm->cmd.rightmove < 0 ) ) { + pm->ps->velocity[2] *= 5; + } + + // fix high speed bug (some strafe - or defrag trick) by touching and jumping off the water surface + vel = VectorLength( pm->ps->velocity ); + if ( vel > maxSpeed ) { // keep maximum speed + vel = maxSpeed; + } + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + + // BFP - Handle PMF flag + pm->ps->pm_flags &= ~PMF_AIR_GRAVITY; + + PM_ForceJumpAnim(); + return; + } + + // BFP - No ground trace handling in the water + if ( pm->waterlevel > 1 ) { + return; + } + + VectorCopy( pm->ps->origin, point ); + point[2] -= 95; // BFP - Put more down, obviously it was 64, but BFP does that + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // BFP - Falling distantly from the ground + if ( trace.fraction == 1.0 && !( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMP + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMPB ) + && !( pm->ps->eFlags & EF_FLIGHT ) ) { + PM_ForceJumpAnim(); + PM_TorsoStatusAnim( TORSO_STAND ); + } - // BFP - TODO: torso animations may not be needed here, - // possibly weaponstates could be replaced/removed - // or this function could be replaced/removed + // If idling, keep the torso + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { + PM_TorsoStatusAnim( TORSO_STAND ); + } + // BFP - Melee strike legs animation, don't apply if it isn't touching the ground + PM_ContinueMeleeStrikeLegsAnim( pm->ps->groundEntityNum != ENTITYNUM_NONE ); + +#if 0 if ( pm->ps->weaponstate == WEAPON_READY ) { if ( pm->ps->weapon == WP_GAUNTLET ) { PM_ContinueTorsoAnim( TORSO_STAND ); // BFP - before TORSO_STAND2 @@ -1524,83 +2611,388 @@ static void PM_TorsoAnimation( void ) { } return; } +#endif +} + +/* +============== +PM_CheckFlightState +============== +*/ +static void PM_CheckFlightState( pmove_t *pm ) { // BFP - Checks if the flight is disabled while the key is held + if ( pm->ps->pm_type == PM_DEAD || pm->ps->pm_type == PM_SPECTATOR + || ( pm->ps->pm_flags & PMF_RESPAWNED ) ) { + pm->ps->pm_flags &= ~PMF_FLIGHT_LATCH; + // add a small fall while respawning and holding the key + if ( ( pm->ps->pm_flags & PMF_RESPAWNED ) && ( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) ) { + pm->ps->velocity[2] -= 150; + } else { + pm->cmd.buttons &= ~BUTTON_ENABLEFLIGHT; + } + return; + } + + if ( ( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) && !pm->noFlight ) { + if ( !( pm->ps->pm_flags & PMF_FLIGHT_LATCH ) ) { + // do not play the sound in the charging status + if ( !( pm->ps->eFlags & EF_FLIGHT ) && !( pm->ps->pm_flags & PMF_KI_CHARGE ) ) { + BG_AddPredictableEventToPlayerstate( EV_ENABLE_FLIGHT, 0, pm->ps, -1 ); // play the sound + } + // change state and lock until release + pm->ps->eFlags ^= EF_FLIGHT; + pm->ps->pm_flags |= PMF_FLIGHT_LATCH; + } + } else { + pm->ps->pm_flags &= ~PMF_FLIGHT_LATCH; + } + + if ( pm->ps->eFlags & EF_FLIGHT ) { + pm->cmd.buttons |= BUTTON_ENABLEFLIGHT; + } else { + pm->cmd.buttons &= ~BUTTON_ENABLEFLIGHT; + } +} + +/* +============== +PM_FlightStart +============== +*/ +static void PM_FlightStart( void ) { // BFP - Start flight handling + vec3_t point; + trace_t trace; + + // BFP - Hit stun + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) { + return; + } + + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + + // BFP - Avoid entering jump flying status after recharging ki + if ( !( pm->ps->pm_flags & PMF_KI_CHARGE ) && !( pm->cmd.buttons & BUTTON_KI_CHARGE ) + && pm->ps->pm_time > 0 ) { + return; + } + + 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; + + // BFP - If the player is in the ground, then jump! + // And make sure when the player isn't flying and falling + if ( ( ( pm->ps->eFlags & EF_FLIGHT ) || ( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) ) // handle the flight button if it's being pressed, that avoids jittering + && ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLECR + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_WALK + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_WALKCR + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_BACK + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_RUN ) + && ( pml.groundTrace.contents & MASK_PLAYERSOLID ) + && !( pm->cmd.buttons & BUTTON_KI_CHARGE ) + && !( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE ) + && pm->ps->weaponstate != WEAPON_STUN ) { + pm->ps->pm_time = 1120; // to avoid drifting while standing the jump velocity + pm->ps->velocity[2] = JUMP_VELOCITY - 200; + + // don't play the animation when being transformed + if ( !( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) { + if ( !PM_IsInKiAttackState() + && !( pm->ps->pm_flags & PMF_MELEE ) ) { + if ( pm->cmd.forwardmove > 0 ) { + PM_TorsoStatusAnim( TORSO_FLYA ); + } else if ( pm->cmd.forwardmove < 0 ) { + PM_TorsoStatusAnim( TORSO_FLYB ); + } else { + PM_TorsoStatusAnim( TORSO_STAND ); + } + } + PM_ForceLegsAnim( LEGS_JUMP ); + } + } +} + +/* +============== +PM_FlightAnimation +============== +*/ +static void PM_FlightAnimation( void ) { // BFP - Flight + + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + + if ( ( ( pm->ps->eFlags & EF_FLIGHT ) || ( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) ) + && pm->ps->pm_time <= 570 ) { // smooth jump animation + pm->ps->pm_flags |= PMF_AIR_GRAVITY; // BFP - Air gravity + PM_ContinueFlyAnim(); + return; + } + + // BFP - That happens when the player is landing nearly + if ( !( pm->ps->eFlags & EF_FLIGHT ) + && !( pm->ps->pm_flags & PMF_JUMP_HELD ) + && pm->ps->groundEntityNum == ENTITYNUM_NONE // hasn't touched the ground yet + && ( pml.groundTrace.contents & MASK_PLAYERSOLID ) ) { + PM_SlopesNeargroundAnim( 0 ); + } + + if ( !( pml.groundTrace.contents & MASK_PLAYERSOLID ) + && !( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMP + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMPB ) + && !( pm->ps->eFlags & EF_FLIGHT ) + && pm->waterlevel < 1 ) { + if ( pm->cmd.forwardmove < 0 ) { // when failing backwards after flying + PM_StartLegsAnim( LEGS_JUMPB ); + } else { + PM_StartLegsAnim( LEGS_JUMP ); + } + PM_TorsoStatusAnim( TORSO_STAND ); + } +} + +/* +============== +PM_KiChargeAnimation +============== +*/ +static void PM_KiChargeAnimation( void ) { // BFP - Ki Charge + + // stop charging if it's using ki boost + if ( ( pm->cmd.buttons & BUTTON_KI_USE ) && ( pm->cmd.buttons & BUTTON_KI_CHARGE ) ) { + // handle the button to avoid toggling the animations forward and backwards while using ki boost + pm->cmd.buttons &= ~BUTTON_KI_CHARGE; + } + + if ( ( pm->cmd.buttons & BUTTON_KI_USE ) && ( pm->ps->pm_flags & PMF_KI_CHARGE ) ) { + pm->ps->pm_flags &= ~PMF_KI_CHARGE; + pm->ps->pm_time = 0; + // do jump animation if it's falling + if ( !( pml.groundTrace.contents & MASK_PLAYERSOLID ) + && !( pm->ps->eFlags & EF_FLIGHT ) && !( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) + && !( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMP + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMPB ) + && pm->waterlevel <= 1 // Don't force inside the water + && !( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) { // avoid forcing animations on transformation phase + PM_ForceJumpAnim(); + PM_ContinueTorsoAnim( TORSO_STAND ); // Keep the torso + } + return; + } + + if ( ( pm->ps->pm_flags & PMF_KI_CHARGE ) && !( pm->cmd.buttons & BUTTON_KI_CHARGE ) ) { + pm->ps->pm_time = 200; // Make sure to avoid entering jump flying status after recharging ki + pm->ps->eFlags &= ~EF_AURA; // Make sure the aura is off, otherwise the ki use proceeds + pm->ps->pm_flags &= ~PMF_KI_CHARGE; + if ( !( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) { // avoid forcing animations on transformation phase + PM_ContinueLegsAnim( LEGS_IDLE ); // Keep the legs when being near to the ground at that height + } + // do jump animation if it's falling + if ( !( pml.groundTrace.contents & MASK_PLAYERSOLID ) + && !( pm->ps->eFlags & EF_FLIGHT ) + && !( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMP + || ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_JUMPB ) + && pm->waterlevel <= 1 // don't force inside the water + && ( !( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) ) { // avoid forcing animations on transformation phase + PM_ForceJumpAnim(); + PM_ContinueTorsoAnim( TORSO_STAND ); // Keep the torso + } + } + + if ( pm->cmd.buttons & BUTTON_KI_CHARGE ) { + if ( !( pm->ps->pm_flags & PMF_KI_CHARGE ) ) { + pm->ps->eFlags &= ~EF_AURA; // Make sure the aura is off, otherwise the visual charging effect continues without handling correctly + } + pm->ps->eFlags &= ~EF_KI_BOOST; + pm->ps->eFlags &= ~EF_FIRING; // don't display shooting effects + pm->ps->pm_flags |= PMF_KI_CHARGE; + if ( !( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) { // avoid forcing animations on transformation phase + PM_ContinueTorsoAnim( TORSO_CHARGE ); + PM_ContinueLegsAnim( LEGS_CHARGE ); + } + } + + // handle the button to avoid toggling ki boost when already used "kiusetoggle" key bind + if ( ( pm->cmd.buttons & BUTTON_KI_USE ) && ( pm->ps->eFlags & EF_KI_BOOST ) ) { + pm->ps->eFlags &= ~EF_KI_BOOST; + } +} + +/* +============== +PM_UltimateTierTransformAnimation +============== +*/ +static void PM_UltimateTierTransformAnimation( void ) { // BFP - Ultimate Tier transform animation + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + pm->ps->pm_flags &= ~( PMF_BLOCK | PMF_MELEE ); + pm->ps->eFlags &= ~EF_FIRING; + pm->ps->weaponstate = WEAPON_READY; + pm->cmd.buttons &= ~( BUTTON_ATTACK | BUTTON_WALKING | BUTTON_GESTURE | BUTTON_USE_HOLDABLE | BUTTON_MELEE | BUTTON_BLOCK ); + pm->cmd.forwardmove = pm->cmd.rightmove = pm->cmd.upmove = 0; + pm->ps->viewangles[PITCH] = 0; + pm->ps->viewangles[ROLL] = 0; + PM_ContinueTorsoAnim( TORSO_CHARGE ); + PM_ContinueLegsAnim( LEGS_CHARGE ); + } +} + +/* +============== +PM_HitStunAnimation +============== +*/ +static void PM_HitStunAnimation( void ) { // BFP - Hit stun + + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) { + PM_StartTorsoAnim( TORSO_STUN ); + PM_StartLegsAnim( LEGS_IDLECR ); + } +} + +/* +============== +PM_Melee +============== +*/ +static void PM_Melee( void ) { // BFP - Melee + // Don't allow pressing ki attack and block buttons when melee is being used + if ( ( pm->ps->pm_flags & PMF_MELEE ) + || ( pm->cmd.buttons & BUTTON_MELEE ) ) { + pm->ps->eFlags &= ~EF_FIRING; // don't display shooting effects + pm->ps->eFlags &= ~EF_READY_KI_ATTACK; // leave ready ki attack + pm->cmd.buttons &= ~( BUTTON_ATTACK | BUTTON_BLOCK ); + } } /* ============== -PM_FlightAnimation +PM_KiExplosionWave + +Handle ki explosion wave during and at the end of its use ============== */ -static void PM_FlightAnimation( void ) { // BFP - Flight - if ( pm->ps->pm_flags & PMF_FLYING ) { - - // make sure to handle the PM_flag - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - - if ( pm->cmd.forwardmove > 0 ) { - PM_StartTorsoAnim( TORSO_FLYA ); - PM_ForceLegsAnim( LEGS_FLYA ); - PM_StartLegsAnim( LEGS_FLYA ); - } else if ( pm->cmd.forwardmove < 0 ) { - PM_StartTorsoAnim( TORSO_FLYB ); - PM_ForceLegsAnim( LEGS_FLYB ); - PM_StartLegsAnim( LEGS_FLYB ); - } else { - // PM_ForceLegsAnim( LEGS_FLYIDLE ); - PM_ContinueLegsAnim( LEGS_FLYIDLE ); - } +static void PM_KiExplosionWave( void ) { // BFP - Ki explosion wave handling + // ki explosion wave state + if ( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE + && ( pm->ps->eFlags & EF_FIRING ) ) { + // don't move, also these keys cannot be used; blocking, ki charge and ki boost statuses are removed + pm->cmd.forwardmove = pm->cmd.rightmove = pm->cmd.upmove = 0; + pm->cmd.buttons &= ~( BUTTON_KI_USE | BUTTON_KI_CHARGE | BUTTON_BLOCK ); + pm->ps->pm_flags &= ~( PMF_KI_CHARGE | PMF_BLOCK ); + pm->ps->pm_flags |= PMF_JUMP_HELD; // avoid pressing jump key + pm->ps->eFlags &= ~EF_KI_BOOST; return; } - // Handle the player movement animation if trying to change quickly the direction of forward or backward - if ( !pm->isFlying && !( pml.groundTrace.contents & CONTENTS_SOLID ) - && ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) ) { + // ki explosion wave stun state + if ( pm->ps->weaponstate == WEAPON_STUN ) { + // don't move, also these keys cannot be used; melee, attacking, blocking, ki charge and ki boost statuses are removed + pm->cmd.forwardmove = pm->cmd.rightmove = pm->cmd.upmove = 0; + pm->cmd.buttons &= ~( BUTTON_ATTACK | BUTTON_KI_CHARGE | BUTTON_KI_USE | BUTTON_BLOCK | BUTTON_MELEE ); + pm->ps->pm_flags &= ~( PMF_KI_CHARGE | PMF_BLOCK | PMF_MELEE ); + pm->ps->pm_flags |= PMF_JUMP_HELD; // avoid pressing jump key + pm->ps->eFlags &= ~EF_KI_BOOST; + return; + } +} - // stops entering again here and don't change the animation to backwards/forward - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; +/* +============== +PM_BeamStruggleStatus - if ( pm->cmd.forwardmove > 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - PM_StartLegsAnim( LEGS_JUMP ); - } else if ( pm->cmd.forwardmove < 0 ) { // when failing backwards after flying - PM_ForceLegsAnim( LEGS_JUMPB ); - PM_StartLegsAnim( LEGS_JUMPB ); - } else { - PM_ForceLegsAnim( LEGS_JUMP ); - PM_StartLegsAnim( LEGS_JUMP ); +Handle beam struggle status +============== +*/ +static void PM_BeamStruggleStatus( void ) { // BFP - Beam struggle handling + if ( pm->ps->weaponstate == WEAPON_BEAMSTRUGGLE ) { + // don't move, also these keys cannot be used + pm->cmd.forwardmove = pm->cmd.rightmove = pm->cmd.upmove = 0; + } +} + + +/* +=========== +PM_KiConsumption +=========== +*/ +static void PM_KiConsumption( int addTime, int kiConsume ) { // BFP - Ki consumption when using ki attacks + if ( pm->ps->stats[STAT_KI] >= 0 ) { // avoid consuming more ki than available + pm->ps->stats[STAT_KI] -= kiConsume; + if ( pm->ps->stats[STAT_KI] < 0 ) { + pm->ps->stats[STAT_KI] = 0; } + pm->ps->weaponTime += addTime; + } else { // not enough ki + pm->ps->eFlags &= ~EF_FIRING; + pm->ps->eFlags &= ~EF_READY_KI_ATTACK; } } + /* -============== -PM_KiChargeAnimation -============== +=========== +PM_KiCost +=========== */ -static void PM_KiChargeAnimation( void ) { // BFP - Ki Charge - if ( pm->cmd.buttons & BUTTON_KI_CHARGE ) { - PM_StartTorsoAnim( TORSO_CHARGE ); - //PM_ForceLegsAnim( LEGS_CHARGE ); - PM_StartLegsAnim( LEGS_CHARGE ); +static float PM_KiCost( void ) { // BFP - kiCost, kiCostAsPct and kiPct + float kiCost = ( pm->kiCost > 0 ) ? pm->kiCost : 0; + float kiPct = ( pm->kiPct > 1 ) ? 1 : pm->kiPct; + if ( pm->kiCostAsPct && kiPct > 0 ) { + kiCost = pm->ps->stats[STAT_MAX_KI] * kiPct; } + return kiCost; } + /* -============== -PM_HitStunAnimation -============== +============ +PM_ChargeKiAttackState +============ */ -static void PM_HitStunAnimation( void ) { // BFP - Hit stun +static void PM_ChargeKiAttackState( int minCharge, int maxCharge, int addTime, int kiConsume ) { // BFP - Charge ki attack state + const int ATTACK_CHARGE_LIMIT = 6; - // When the player doesn't have more ki, play hit stun animation - if ( pm->ps->stats[STAT_KI] <= 0 - && pm->ps->pm_type != PM_DEAD - && pm->ps->pm_type != PM_SPECTATOR ) { - PM_StartTorsoAnim( TORSO_STUN ); - PM_ForceLegsAnim( LEGS_IDLECR ); + PM_KiConsumption( addTime, kiConsume ); + if ( !pm->chargeAttack && !pm->chargeAutoFire ) { + return; + } + if ( ( pm->chargeAutoFire + || maxCharge <= minCharge + || ( maxCharge > 0 && pm->ps->generic1 < maxCharge ) ) + && pm->ps->generic1 < ATTACK_CHARGE_LIMIT ) { + ++pm->ps->generic1; } + if ( pm->ps->generic1 >= minCharge && !pm->chargeAutoFire ) { + pm->ps->eFlags |= EF_READY_KI_ATTACK; + } +} + +/* +============ +PM_RandomWeaponTime +============ +*/ +static int PM_RandomWeaponTime( void ) { // BFP - randomWeaponTime calculation + int weaponTime = pm->weaponTime; + int randomWeaponTime = 0; + if ( pm->randomWeaponTime > 0 ) { + randomWeaponTime = random() * pm->randomWeaponTime; + } + weaponTime += randomWeaponTime; + return weaponTime; } + /* ============== PM_Weapon @@ -1609,7 +3001,19 @@ Generates weapon events and modifes the weapon counter ============== */ static void PM_Weapon( void ) { - int addTime; + // BFP - HIGHLY MODIFIED + // BFP - Ki cost + float kiCost = PM_KiCost(); + // BFP - Weapon time + int weaponTime = PM_RandomWeaponTime(); + + // BFP - Hit stun and ultimate tier, avoid shooting if the player is in this status + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 || ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) ) { + pm->ps->eFlags &= ~EF_READY_KI_ATTACK; + pm->ps->generic1 = 0; + pm->ps->weaponTime = 0; + return; + } // don't allow attack until all buttons are up if ( pm->ps->pm_flags & PMF_RESPAWNED ) { @@ -1631,7 +3035,7 @@ static void PM_Weapon( void ) { if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT - && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { + && pm->ps->stats[STAT_HEALTH] >= pm->ps->stats[STAT_MAX_HEALTH] ) { // BFP - Before Q3: + 25 // don't use medkit if at max health } else { pm->ps->pm_flags |= PMF_USE_ITEM_HELD; @@ -1644,7 +3048,6 @@ static void PM_Weapon( void ) { pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; } - // make weapon function if ( pm->ps->weaponTime > 0 ) { pm->ps->weaponTime -= pml.msec; @@ -1653,139 +3056,287 @@ static void PM_Weapon( void ) { // check for weapon change // can't change if weapon is firing, but can change // again if lowering or raising - if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if ( pm->ps->weaponstate != WEAPON_ACTIVE // BFP - Avoid if the beam/rdmissile/forcefield is still firing + && pm->ps->weaponstate != WEAPON_STUN // BFP - Avoid when being stunned + && ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) ) { if ( pm->ps->weapon != pm->cmd.weapon ) { PM_BeginWeaponChange( pm->cmd.weapon ); } } - if ( pm->ps->weaponTime > 0 ) { - return; - } - // change weapon if time - if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + if ( pm->ps->weaponstate == WEAPON_DROPPING + && pm->ps->weaponTime <= 0 ) { // BFP - Handling ki attack animations when these already shoot PM_FinishWeaponChange(); return; } - if ( pm->ps->weaponstate == WEAPON_RAISING ) { - pm->ps->weaponstate = WEAPON_READY; - if ( pm->ps->weapon == WP_GAUNTLET ) { - PM_StartTorsoAnim( TORSO_STAND ); // BFP - before TORSO_STAND2 - } else { - PM_StartTorsoAnim( TORSO_STAND ); + // BFP - Melee, avoid shooting if the player is in this status + if ( ( pm->cmd.buttons & BUTTON_MELEE ) + && !( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE ) + && pm->ps->weaponstate != WEAPON_STUN ) { + // only use when there's no splitting ki ball until it has been splitted or collided, + // unless if the player wanna change the weapon from this state + if ( pm->ps->weaponstate != WEAPON_ACTIVE ) { + pm->ps->weaponstate = WEAPON_READY; + pm->ps->generic1 = 0; + } + // Melee fight handling + if ( pm->meleeHit && pm->ps->weaponTime <= 0 ) { + int rndSnd = rand() % 6; + pm->ps->weaponTime += 300; + pm->ps->pm_flags |= PMF_MELEE; + // melee sound event is randomly executed + if ( rndSnd > 3 ) { + PM_AddEvent( EV_MELEE ); + } } return; } - // check for fire - if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - - // start the animation even if out of ammo - if ( pm->ps->weapon == WP_GAUNTLET ) { - // the guantlet only "fires" when it actually hits something - if ( !pm->gauntletHit ) { + // BFP - Weapon states, Q3 doesn't have this way + switch( pm->ps->weaponstate ) { + case WEAPON_READY: + pm->ps->eFlags &= ~EF_READY_KI_ATTACK; + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) ) { + pm->ps->generic1 = 0; + } else { + if ( pm->ps->weaponTime <= 0 ) { + // BFP - sbeam attack type + if ( pm->attackType == ATK_SBEAM ) { + pm->ps->weaponTime += weaponTime; + pm->ps->stats[STAT_KI] -= kiCost; + pm->ps->weaponstate = WEAPON_ACTIVE; + // fire and make a sound + PM_AddEvent( EV_FIRE_WEAPON ); + break; + } + if ( pm->chargeAttack || pm->chargeAutoFire ) { + pm->ps->weaponTime += weaponTime; + } + pm->ps->weaponstate = WEAPON_FIRING; + } + } + break; + case WEAPON_DROPPING: + case WEAPON_RAISING: + if ( pm->ps->weaponTime <= 0 ) { pm->ps->weaponTime = 0; + pm->ps->generic1 = 0; pm->ps->weaponstate = WEAPON_READY; - return; } - PM_StartTorsoAnim( TORSO_STAND ); - } else { - PM_StartTorsoAnim( TORSO_STAND ); - } - - pm->ps->weaponstate = WEAPON_FIRING; + break; + case WEAPON_FIRING: + // don't allow ki charging while charging the attack + if ( ( pm->ps->pm_flags & PMF_KI_CHARGE ) || ( pm->cmd.buttons & BUTTON_KI_CHARGE ) ) { + pm->ps->weaponstate = WEAPON_READY; + pm->ps->weaponTime = 0; + break; + } -// BFP - disable ammo stuff, take ki instead \o/ -#if 0 - // check for out of ammo - if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { - PM_AddEvent( EV_NOAMMO ); - pm->ps->weaponTime += 500; - return; - } + // BFP - There are charging states here + if ( pm->chargeAttack && !pm->chargeAutoFire ) { + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) ) { + // BFP - When the ki attack is fully charged, enter beam firing state + // or enter splitting ki ball firing state if it's a splitting ki ball + pm->ps->eFlags &= ~EF_READY_KI_ATTACK; + // no fully charged, skip... + if ( pm->ps->generic1 < pm->minCharge ) { + pm->ps->weaponstate = WEAPON_READY; + pm->ps->weaponTime = 0; + break; + } - // take an ammo away if not infinite - if ( pm->ps->ammo[pm->ps->weapon] != -1 ) { - pm->ps->ammo[pm->ps->weapon]--; - } -#endif + // handle the animation for the start of beam or ball shoot + switch( pm->attackType ) { + case ATK_MISSILE: + pm->ps->weaponstate = WEAPON_READY; + pm->ps->weaponTime += weaponTime; + break; + case ATK_RDMISSILE: + case ATK_BEAM: + pm->ps->weaponstate = WEAPON_ACTIVE; + pm->ps->weaponTime += weaponTime; + break; + case ATK_FORCEFIELD: + pm->ps->eFlags |= EF_FIRING; + pm->ps->weaponstate = WEAPON_ACTIVE; + pm->ps->weaponTime += weaponTime; + } - // fire weapon - PM_AddEvent( EV_FIRE_WEAPON ); + // fire and make a sound + PM_AddEvent( EV_FIRE_WEAPON ); + } + } - // BFP - TODO: - // 1st attackset simple ki shot attack: - // pm->ps->stats[STAT_KI] -= 10; - - // 2nd attackset second simple ki shot attack: - // pm->ps->stats[STAT_KI] -= 100; - - // 3rd attackset ki blasts: - // pm->ps->stats[STAT_KI] -= 240; + // chargeAutoFire: keep firing while attack key is holding + if ( pm->chargeAutoFire + && pm->ps->weaponTime <= 0 ) { + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) + || ( pm->cmd.buttons & BUTTON_MELEE ) + || pm->ps->weapon != pm->cmd.weapon ) { // avoid when changing weapon + pm->ps->weaponstate = WEAPON_READY; + break; + } + if ( pm->cmd.buttons & BUTTON_ATTACK ) { + pm->ps->eFlags |= EF_FIRING; + PM_AddEvent( EV_FIRE_WEAPON ); + } + if ( pm->attackType == ATK_BEAM + || pm->attackType == ATK_FORCEFIELD + || pm->attackType == ATK_RDMISSILE ) { + pm->ps->weaponstate = WEAPON_ACTIVE; + } + } - // 4th attackset charging some ki attack: - // pm->ps->stats[STAT_KI] -= 250; // reduces every time you stand until you shot or stop - - // 5th attackset charging some ki attack: - // pm->ps->stats[STAT_KI] -= 1000; // reduces every time you stand until you shot or stop + // check for fire + if ( pm->ps->weaponTime <= 0 && ( pm->cmd.buttons & BUTTON_ATTACK ) + && ( pm->chargeAttack || pm->chargeAutoFire ) ) { + PM_ChargeKiAttackState( pm->minCharge, pm->maxCharge, weaponTime, kiCost ); + } + if ( !pm->chargeAttack && !pm->chargeAutoFire + && pm->ps->weaponTime <= 0 ) { + if ( pm->attackType == ATK_HITSCAN ) { + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) ) { + pm->ps->weaponstate = WEAPON_READY; + break; + } + if ( pm->cmd.buttons & BUTTON_ATTACK ) { + pm->ps->eFlags |= EF_FIRING; + PM_AddEvent( EV_FIRE_WEAPON ); + PM_KiConsumption( weaponTime, kiCost ); + pm->ps->weaponstate = WEAPON_READY; + } + break; + } - switch( pm->ps->weapon ) { - default: - case WP_GAUNTLET: - addTime = 400; - pm->ps->stats[STAT_KI] -= 0; - break; - case WP_MACHINEGUN: - addTime = 100; - pm->ps->stats[STAT_KI] -= 50; - break; - case WP_SHOTGUN: - addTime = 1000; - pm->ps->stats[STAT_KI] -= 100; - break; - case WP_GRENADE_LAUNCHER: - addTime = 800; - pm->ps->stats[STAT_KI] -= 100; - break; - case WP_ROCKET_LAUNCHER: - addTime = 800; - pm->ps->stats[STAT_KI] -= 50; - break; - case WP_LIGHTNING: - addTime = 50; - pm->ps->stats[STAT_KI] -= 70; - break; - case WP_PLASMAGUN: - addTime = 100; - pm->ps->stats[STAT_KI] -= 120; - break; - case WP_RAILGUN: - addTime = 1500; - pm->ps->stats[STAT_KI] -= 150; - break; - case WP_BFG: - addTime = 200; - pm->ps->stats[STAT_KI] -= 20; + pm->ps->eFlags |= EF_FIRING; + PM_AddEvent( EV_FIRE_WEAPON ); + PM_KiConsumption( weaponTime, kiCost ); + if ( pm->attackType == ATK_BEAM ) { + pm->ps->weaponstate = WEAPON_ACTIVE; + } + if ( pm->attackType == ATK_MISSILE + || pm->attackType == ATK_RDMISSILE ) { + pm->ps->weaponstate = WEAPON_READY; + } + } break; - case WP_GRAPPLING_HOOK: - addTime = 400; - pm->ps->stats[STAT_KI] -= 100; + // BFP - NOTE: The beam is triggering until pressing the attack key again after holded, using ki charge or blocking + // Pressing attack key again or changing weapon, the beam is exploded before the impact + case WEAPON_ACTIVE: + // BFP - NOTE: Lock movement during beam struggle + case WEAPON_BEAMSTRUGGLE: + pm->ps->eFlags |= EF_FIRING; // keep playing firing sound + + // chargeAutoFire: keep firing while attack key is holding + if ( pm->attackType == ATK_BEAM + && pm->chargeAutoFire ) { + if ( ( pm->cmd.buttons & BUTTON_ATTACK ) + && pm->ps->weaponTime <= 0 ) { + // PM_AddEvent( EV_FIRE_WEAPON ); + PM_ChargeKiAttackState( pm->minCharge, pm->maxCharge, weaponTime, kiCost ); + } + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) + || ( ( pm->ps->pm_flags & PMF_KI_CHARGE ) && ( pm->ps->eFlags & EF_AURA ) ) + || ( pm->ps->pm_flags & PMF_BLOCK ) ) { + pm->ps->weaponstate = WEAPON_READY; + } + break; + } + + if ( ( pm->chargeAttack || pm->chargeAutoFire ) + && pm->attackType == ATK_FORCEFIELD ) { + if ( ( pm->cmd.buttons & BUTTON_ATTACK ) + && pm->ps->weaponTime <= 0 ) { + PM_AddEvent( EV_FIRE_WEAPON ); + PM_ChargeKiAttackState( pm->minCharge, pm->maxCharge, weaponTime, kiCost ); + } + pm->ps->eFlags &= ~( EF_AURA | EF_KI_BOOST ); + + // fall even whether the player is flying + if ( pm->ps->eFlags & EF_FLIGHT ) { + pm->ps->velocity[2] -= pm->ps->gravity * 2 * pml.frametime; + } + + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) + || ( pm->cmd.buttons & BUTTON_MELEE ) + || pm->ps->weapon != pm->cmd.weapon ) { // avoid when changing weapon + pm->ps->weaponTime = pm->movementPenalty; + pm->ps->eFlags &= ~EF_FIRING; + if ( pm->movementPenalty > 0 ) { + pm->ps->weaponstate = WEAPON_STUN; + } else { + pm->ps->weaponstate = WEAPON_READY; + } + } + break; + } + + if ( pm->attackType == ATK_RDMISSILE + && pm->chargeAutoFire ) { + if ( ( pm->cmd.buttons & BUTTON_ATTACK ) + && pm->ps->weaponTime <= 0 ) { + PM_AddEvent( EV_FIRE_WEAPON ); + PM_ChargeKiAttackState( pm->minCharge, pm->maxCharge, weaponTime, kiCost ); + } + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) + || ( pm->cmd.buttons & BUTTON_MELEE ) + || pm->ps->weapon != pm->cmd.weapon ) { // avoid when changing weapon + pm->ps->weaponstate = WEAPON_READY; + pm->ps->weaponTime = 0; + } + break; + } + pm->ps->generic1 = 0; + + // BFP - sbeam attack type + if ( pm->attackType == ATK_SBEAM ) { + if ( pm->ps->weaponTime <= 0 ) { + pm->ps->stats[STAT_KI] -= kiCost; + pm->ps->weaponTime += weaponTime; + } + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) + || ( ( pm->ps->pm_flags & PMF_KI_CHARGE ) && ( pm->ps->eFlags & EF_AURA ) ) + || ( pm->cmd.buttons & BUTTON_MELEE ) ) { + pm->ps->weaponstate = WEAPON_READY; + } + break; + } + + if ( pm->attackType == ATK_BEAM + && ( ( pm->cmd.buttons & BUTTON_ATTACK ) + || ( ( pm->ps->pm_flags & PMF_KI_CHARGE ) && ( pm->ps->eFlags & EF_AURA ) ) + || ( pm->ps->pm_flags & PMF_BLOCK ) ) ) { + pm->ps->weaponstate = WEAPON_READY; + } break; - } + case WEAPON_STUN: + if ( pm->ps->weaponTime <= 0 ) { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + } + pm->ps->generic1 = 0; - // BFP - Ki Use - if ( pm->cmd.buttons & BUTTON_KI_USE ) { - addTime /= 1.3; + // fall even whether the player is flying + if ( pm->ps->eFlags & EF_FLIGHT ) { + pm->ps->velocity[2] -= pm->ps->gravity * 2 * pml.frametime; + } } - - pm->ps->weaponTime += addTime; + + // debug print about weapon states and weapon time +#if 0 + 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_FIRING: Com_Printf( "WEAPON_FIRING\n" ); break; + case WEAPON_ACTIVE: Com_Printf( "WEAPON_ACTIVE\n" ); break; + case WEAPON_STUN: Com_Printf( "WEAPON_STUN\n" ); break; + } + Com_Printf( "weaponTime: %d\n", pm->ps->weaponTime ); +#endif } /* @@ -1795,6 +3346,12 @@ PM_Animate */ static void PM_Animate( void ) { + + // BFP - Ultimate tier + if ( pm->ps->pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + if ( pm->cmd.buttons & BUTTON_GESTURE ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_GESTURE ); @@ -1821,6 +3378,11 @@ static void PM_DropTimers( void ) { } } + // BFP - Hit stun time + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) { + pm->ps->stats[STAT_HITSTUN_TIME] -= pml.msec; + } + // drop animation counter if ( pm->ps->legsTimer > 0 ) { pm->ps->legsTimer -= pml.msec; @@ -1857,12 +3419,10 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { return; // no view changes at all } - // Com_Printf( "pml.groundPlane == qtrue: %d\n", pml.groundPlane == qtrue ? "true" : "false" ); - // circularly clamp the angles with deltas for (i=0 ; i<3 ; i++) { temp = cmd->angles[i] + ps->delta_angles[i]; - if ( i == PITCH && !( ps->pm_flags & PMF_FLYING ) ) { // BFP - Avoid that when flying + if ( i == PITCH && !( ps->eFlags & EF_FLIGHT ) ) { // BFP - Avoid that when flying // don't let the player look up or down more than 90 degrees if ( temp > 16000 ) { ps->delta_angles[i] = 16000 - cmd->angles[i]; @@ -1885,20 +3445,13 @@ Enables/disables flight */ static qboolean PM_EnableFlight( void ) { // BFP - Flight - if ( !( pm->ps->pm_flags & PMF_FLYING ) ) { - if ( pm->isFlying && ( pml.groundTrace.contents & CONTENTS_SOLID ) ) { - pm->isFlying = qfalse; - } + // BFP - Hit stun, avoid enabling flight if the player is in this status + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) { return qfalse; } - if ( pm->ps->pm_flags & PMF_FLYING ) { - if ( !pm->isFlying && ( pml.groundTrace.contents & CONTENTS_SOLID ) - && pm->ps->groundEntityNum != ENTITYNUM_NONE ) { - pm->ps->velocity[2] = JUMP_VELOCITY; - } - pm->isFlying = qtrue; - pm->ps->groundEntityNum = ENTITYNUM_NONE; + if ( !( pm->ps->eFlags & EF_FLIGHT ) && !( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) ) { + return qfalse; } return qtrue; @@ -1911,21 +3464,42 @@ PM_KiCharge Charges ki ================ */ -static void PM_KiCharge( pmove_t *pmove ) { // BFP - Ki Charge - pm = pmove; +static void PM_KiCharge( void ) { // BFP - Ki Charge + + // BFP - Hit stun, avoid charging if the player is in this status + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 ) { + return; + } - if ( pmove->cmd.buttons & ( BUTTON_ATTACK | BUTTON_MELEE | BUTTON_KI_USE | BUTTON_BLOCK | BUTTON_ENABLEFLIGHT ) ) { - pmove->cmd.buttons &= ~( BUTTON_ATTACK | BUTTON_MELEE | BUTTON_KI_USE | BUTTON_BLOCK | BUTTON_ENABLEFLIGHT ); + // BFP - Ki explosion wave and stun after using it, avoid charging also + if ( ( pm->attackType == ATK_FORCEFIELD && pm->ps->weaponstate == WEAPON_ACTIVE + && ( pm->ps->eFlags & EF_FIRING ) ) + || pm->ps->weaponstate == WEAPON_STUN ) { + return; } - pmove->cmd.forwardmove = 0; - pmove->cmd.rightmove = 0; - pmove->cmd.upmove = 0; + pm->cmd.forwardmove = pm->cmd.rightmove = 0; + + if ( pm->cmd.buttons & ( BUTTON_ATTACK | BUTTON_KI_USE | BUTTON_MELEE | BUTTON_BLOCK | BUTTON_ENABLEFLIGHT ) ) { + pm->cmd.buttons &= ~( BUTTON_ATTACK | BUTTON_KI_USE | BUTTON_MELEE | BUTTON_BLOCK | BUTTON_ENABLEFLIGHT ); + } - pm->ps->velocity[0] = 0; - pm->ps->velocity[1] = 0; - pm->ps->velocity[2] = 0; - pm->ps->stats[STAT_KI]++; + // BFP - Smoothing horizontal and forward/backward fall while ki charging + if ( !( pm->ps->eFlags & EF_FLIGHT ) && !( pm->cmd.buttons & BUTTON_ENABLEFLIGHT ) ) { + float speed = VectorLength( pm->ps->velocity ); + if ( speed > 0 ) { + float control = speed < pm_stopspeed ? pm_stopspeed : speed; + float drop = control * pm_friction * pml.frametime; + + // scale the velocity + float newspeed = speed - drop; + if ( newspeed < 0 ) newspeed = 0; + newspeed /= speed; + + pm->ps->velocity[0] *= newspeed; + pm->ps->velocity[1] *= newspeed; + } + } } /* @@ -1935,40 +3509,17 @@ PM_HitStun Receives hit stun ================ */ -static void PM_HitStun( pmove_t *pmove ) { // BFP - Hit stun - pm = pmove; +static void PM_HitStun( void ) { // BFP - Hit stun - if ( pmove->cmd.buttons & ( BUTTON_ATTACK | BUTTON_MELEE | BUTTON_KI_USE | BUTTON_BLOCK | BUTTON_ENABLEFLIGHT ) ) { - pmove->cmd.buttons &= ~( BUTTON_ATTACK | BUTTON_MELEE | BUTTON_KI_USE | BUTTON_BLOCK | BUTTON_ENABLEFLIGHT ); - } + pm->cmd.buttons &= ~( BUTTON_MELEE | BUTTON_KI_USE | BUTTON_BLOCK | BUTTON_ENABLEFLIGHT ); + pm->cmd.upmove = 0; - pm->ps->pm_flags &= ~PMF_FLYING; - pm->ps->pm_flags &= ~PMF_KI_BOOST; + pm->ps->eFlags &= ~EF_FLIGHT; + pm->ps->eFlags &= ~EF_KI_BOOST; + // don't display shot effects on the stunned status + pm->ps->eFlags &= ~EF_FIRING; pm->ps->eFlags &= ~EF_AURA; - - pmove->cmd.forwardmove = 0; - pmove->cmd.rightmove = 0; - pmove->cmd.upmove = 0; - -// BFP - TODO: Hit stun time, maybe remove this and find another way? -#if 0 - if ( pm->ps->hitStunTime > 0 ) { - pm->ps->hitStunTime -= pml.msec; - return; - } - - // 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; - } - } - */ -#endif + pm->ps->weaponstate = WEAPON_READY; } /* @@ -1991,6 +3542,9 @@ void PmoveSingle (pmove_t *pmove) { pm->watertype = 0; pm->waterlevel = 0; + // BFP - Setup bfp_weapon.cfg + PM_SetupBFP_weaponCfg(); + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies } @@ -2008,12 +3562,40 @@ void PmoveSingle (pmove_t *pmove) { pm->ps->eFlags &= ~EF_TALK; } + // BFP - Handling the PMF flag when stepping the ground and when preparing to attack + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + // BFP - TODO: Set to the first selected weapon + pm->ps->pm_flags &= ~PMF_AIR_GRAVITY; // BFP - Air gravity + } + + // BFP - No flight + if ( pm->noFlight ) { + pm->cmd.buttons &= ~BUTTON_ENABLEFLIGHT; + pm->ps->eFlags &= ~EF_FLIGHT; + } + + // BFP - Melee only + if ( pm->meleeOnly ) { + pm->cmd.buttons &= ~BUTTON_ATTACK; + pm->ps->eFlags &= ~EF_FIRING; + } + + // BFP - When blocking, disable the ki use button, also that avoids jittering + if ( pm->ps->pm_flags & PMF_BLOCK ) { + pm->cmd.buttons &= ~BUTTON_KI_USE; + } + // set the firing flag for continuous beam weapons if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION - && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { + && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] + && pm->ps->weapon != WP_SHOTGUN ) { // BFP - That would be with chargeAttack set pm->ps->eFlags |= EF_FIRING; } else { - pm->ps->eFlags &= ~EF_FIRING; + if ( pm->ps->weapon != WP_SHOTGUN ) { // BFP - That would be with chargeAttack set + pm->ps->eFlags &= ~EF_FIRING; + // BFP - Handle attack button when holding to prepare the attack at the start + pm->cmd.buttons &= ~BUTTON_ATTACK; + } } // clear the respawned flag if attack and use are cleared @@ -2064,31 +3646,46 @@ void PmoveSingle (pmove_t *pmove) { pm->ps->pm_flags &= ~PMF_JUMP_HELD; } + // BFP - No handling PMF_BACKWARDS_RUN +#if 0 // 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; } +#endif if ( pm->ps->pm_type >= PM_DEAD ) { // BFP - If player is dead, disable the following statuses - pm->ps->pm_flags &= ~PMF_FLYING; - pm->ps->pm_flags &= ~PMF_KI_BOOST; + pm->ps->eFlags &= ~EF_FLIGHT; + pm->ps->eFlags &= ~EF_KI_BOOST; pm->ps->eFlags &= ~EF_AURA; -// BFP - NOTE: disabled for notes, don't allow pressing these buttons -#if 0 pm->cmd.buttons &= ~BUTTON_KI_CHARGE; pm->cmd.buttons &= ~BUTTON_KI_USE; -#endif pm->cmd.forwardmove = 0; pm->cmd.rightmove = 0; pm->cmd.upmove = 0; } + // BFP - Ki Charge + if ( ( pmove->cmd.buttons & BUTTON_KI_CHARGE ) + && !( pmove->cmd.buttons & BUTTON_KI_USE ) + && pm->ps->pm_type != PM_DEAD + && pm->ps->pm_type != PM_SPECTATOR ) { + PM_KiCharge(); + } + + // BFP - Hit stun + if ( pm->ps->stats[STAT_HITSTUN_TIME] > 0 + && pm->ps->pm_type != PM_DEAD + && pm->ps->pm_type != PM_SPECTATOR ) { + PM_HitStun(); + } + if ( pm->ps->pm_type == PM_SPECTATOR ) { PM_CheckDuck (); PM_FlyMove (); @@ -2117,6 +3714,9 @@ void PmoveSingle (pmove_t *pmove) { // set mins, maxs, and viewheight PM_CheckDuck (); + // BFP - Flight start + PM_FlightStart(); + // set groundentity PM_GroundTrace(); @@ -2124,37 +3724,16 @@ void PmoveSingle (pmove_t *pmove) { PM_DeadMove (); } - // BFP - Ki Charge - if ( ( pmove->cmd.buttons & BUTTON_KI_CHARGE ) - && ( pm->ps->pm_type != PM_DEAD ) ) { - PM_KiCharge( pmove ); - } + // BFP - Melee + PM_Melee(); - PM_DropTimers(); + // BFP - Ki explosion wave handling + PM_KiExplosionWave(); - // BFP - Hit stun - if ( pm->ps->stats[STAT_KI] <= 0 - && pm->ps->pm_type != PM_DEAD - && pm->ps->pm_type != PM_SPECTATOR ) { - PM_HitStun( pmove ); -// BFP - TODO: Hit stun time, maybe remove this and find another way? -#if 0 - if ( pm->ps->hitStunTime <= 0 ) { - pm->ps->hitStunTime = pml.msec + 1000; // give 1 second to be stunned - } -#endif - } -// BFP - TODO: Hit stun time, maybe remove this and find another way? -#if 0 - if ( pm->ps->hitStunTime == -3 - && pm->ps->pm_type != PM_DEAD - && pm->ps->pm_type != PM_SPECTATOR ) { // when receives attack from a ki boost melee - pm->ps->hitStunTime = pml.msec + 3000; // give 3 seconds to be stunned - } - if ( pm->ps->hitStunTime > 0 ) { - PM_HitStun( pmove ); - } -#endif + // BFP - Beam struggle handling + PM_BeamStruggleStatus(); + + PM_DropTimers(); // BFP - Flight if ( PM_EnableFlight() ) { @@ -2169,9 +3748,13 @@ void PmoveSingle (pmove_t *pmove) { PM_AirMove(); } #endif + // BFP - No handling PMF_TIME_WATERJUMP +#if 0 else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { PM_WaterJumpMove(); - } else if ( pm->waterlevel > 1 ) { + } +#endif + else if ( pm->waterlevel > 1 ) { // swimming PM_WaterMove(); } else if ( pml.walking ) { @@ -2182,6 +3765,9 @@ void PmoveSingle (pmove_t *pmove) { PM_AirMove(); } + // BFP - Fly tilt + PM_FlyTiltView(); + PM_Animate(); // set groundentity, watertype, and waterlevel @@ -2200,6 +3786,9 @@ void PmoveSingle (pmove_t *pmove) { // BFP - Ki Charge animation PM_KiChargeAnimation(); + // BFP - Ultimate Tier transform animation + PM_UltimateTierTransformAnimation(); + // BFP - Hit stun animation PM_HitStunAnimation(); @@ -2209,8 +3798,9 @@ void PmoveSingle (pmove_t *pmove) { // entering / leaving water splashes PM_WaterEvents(); + // BFP - BFP disabled that because the velocity calcualtions aren't correct when timescale is less than 1 // snap some parts of playerstate to save network bandwidth - trap_SnapVector( pm->ps->velocity ); + // trap_SnapVector( pm->ps->velocity ); } @@ -2236,6 +3826,9 @@ void Pmove (pmove_t *pmove) { pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { @@ -2260,8 +3853,4 @@ void Pmove (pmove_t *pmove) { pmove->cmd.upmove = 20; } } - - //PM_CheckStuck(); - } - diff --git a/source/game/bg_public.h b/source/game/bg_public.h index 09988d8..4221b72 100644 --- a/source/game/bg_public.h +++ b/source/game/bg_public.h @@ -40,7 +40,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection -#define LIGHTNING_RANGE 768 +#define LIGHTNING_RANGE 1500 //768 #define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present @@ -99,13 +99,14 @@ typedef enum { GT_TOURNAMENT, // one on one tournament GT_SINGLE_PLAYER, // single player ffa + GT_SURVIVAL, // BFP - Survival "survival": g_gametype 3 + GT_MONSTER, // BFP - Monster "monster" / "oozaru": g_gametype 4 + //-- team games go after this -- GT_TEAM, // team deathmatch + GT_TLMS, // BFP - Team Last Man Standing "lms" / "tlms": g_gametype 6 GT_CTF, // capture the flag - GT_1FCTF, - GT_OBELISK, - GT_HARVESTER, GT_MAX_GAME_TYPE } gametype_t; @@ -132,36 +133,57 @@ typedef enum { PM_SPINTERMISSION // no movement or status bar } pmtype_t; +// BFP - Cannot be more than 16, weaponstate is a 4-bit integer size typedef enum { WEAPON_READY, WEAPON_RAISING, WEAPON_DROPPING, - WEAPON_FIRING + WEAPON_FIRING, + + // BFP - The following weapon states are to handle the movements + // (originally BFP didn't use that because of their abuse of WP_, PW_ and STAT_ stuff in their networking): + WEAPON_ACTIVE, // BFP - Active fire + WEAPON_BEAMSTRUGGLE, // BFP - Beam struggle + WEAPON_STUN // BFP - Stun status (not hit stun) when using ki explosion wave } weaponstate_t; +// BFP - Attack types +#define ATK_MISSILE 0 +#define ATK_RDMISSILE 1 +#define ATK_BEAM 2 +#define ATK_SBEAM 3 +#define ATK_HITSCAN 4 +#define ATK_FORCEFIELD 5 + +// BFP - If you want to keep demo networking, change the way to use the PMF_ flags // pmove->pm_flags #define PMF_DUCKED 1 #define PMF_JUMP_HELD 2 -#define PMF_FLYING 4 // BFP - Flight -#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_BLOCK 4 // BFP - Block +// BFP - PMF_BACKWARDS_JUMP is unused +//#define PMF_BACKWARDS_JUMP 8 // go into backwards land +// BFP - PMF_BACKWARDS_RUN is renamed // coast down to backwards run +#define PMF_MELEE 16 // BFP - Melee +// BFP - PMF_TIME_LAND is unused +//#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_KI_BOOST 128 // BFP - Using Ki -#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump +#define PMF_KI_CHARGE 128 // BFP - Ki charge +// BFP - PMF_TIME_WATERJUMP is renamed // pm_time is waterjump +#define PMF_AIR_GRAVITY 256 // BFP - Air gravity check #define PMF_RESPAWNED 512 // clear after attack and jump buttons come up #define PMF_USE_ITEM_HELD 1024 -// BFP - no hook -// #define PMF_GRAPPLE_PULL 2048 // pull towards grapple location +// BFP - PMF_GRAPPLE_PULL is unused +//#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location #define PMF_FOLLOW 4096 // spectate following another player -// BFP - PMF_SCOREBOARD is unused -// #define PMF_SCOREBOARD 8192 // spectate as a scoreboard -// BFP - TODO: Reuse the following flag (used on Team Arena), change name if it'll be used -#define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size +// BFP - PMF_SCOREBOARD is renamed // spectate as a scoreboard +#define PMF_ULTIMATE_TIER 8192 // BFP - Ultimate tier status +#define PMF_FLIGHT_LATCH 16384 // BFP - Flight latch toggling +// #define PMF_UNUSED_FLAG 32768 // BFP - Some unused pm_flag // BFP - Last pm_flag after 32768. That's the limit of pm_flags, it can't reach more // #define PMF_SOMEFLAG 65536 // some pm_flag -#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) +// BFP - That combination of PMF_TIME_* flags is unused +#define PMF_ALL_TIMES PMF_TIME_KNOCKBACK //(PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) #define MAXTOUCH 32 typedef struct { @@ -173,9 +195,30 @@ typedef struct { int tracemask; // collide against these types of surfaces int debugLevel; // if set, diagnostic output will be printed qboolean noFootsteps; // if the game is setup for no footsteps by the server - qboolean gauntletHit; // true if a gauntlet attack would actually hit something - qboolean isFlying; // BFP - Flight - //qboolean hitStunReceived; // BFP - TODO: Hit stun + qboolean meleeHit; // BFP - before: gauntletHit - true if a melee attack would actually hit something + + qboolean noFlight; // BFP - No flight + qboolean meleeOnly; // BFP - Melee only + + // BFP - TODO: bfp_weapon.cfg + int weaponTime; // BFP - Weapon time + int randomWeaponTime; // BFP - Milliseconds of random extra fire delay + qboolean kiCostAsPct; // BFP - Enables ki cost percentage + float kiPct; // BFP - Ki cost percentage (0.0 - 1.0) + int kiCost; // BFP - Ki cost + + qboolean chargeAttack; // BFP - Charge attack + qboolean chargeAutoFire; // BFP - Charge autofire + int minCharge; // BFP - Minimum charge points + int maxCharge; // BFP - Maximum charge points + + qboolean loopingAnim; // BFP - Looping ki attack animation + qboolean noAttackAnim; // BFP - No prepare ki attack animation + + qboolean railTrail; // BFP - Rail trail weapon check + int attackType; // BFP - Attack type check + + int movementPenalty; // BFP - Seconds of movement penalty int framecount; @@ -211,15 +254,25 @@ void Pmove (pmove_t *pmove); // NOTE: may not have more than 16 typedef enum { STAT_HEALTH, - STAT_KI, // BFP - KI amount STAT_HOLDABLE_ITEM, STAT_WEAPONS, // 16 bit fields STAT_ARMOR, - STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) + // BFP - Got rid of STAT_DEAD_YAW, now uses ps->damageYaw and ps->damagePitch + STAT_UNUSED_INDEX4, // unused stat index (don't remove if you want to keep demo networking!) STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) - STAT_MAX_HEALTH // health / armor limit, changable by handicap -} statIndex_t; + STAT_UNUSED_INDEX6, // BFP - Powerlevel + STAT_UNUSED_INDEX7, // BFP - Flight jump anim transition seconds, maximum is 21 sec and stops changing to 0, even when stop flying also reproduces this stat index like starting to fly (looks weird) + STAT_KI, // BFP - Ki + STAT_MAX_KI, // BFP - Maximum ki + STAT_UNUSED_INDEX10, // BFP - Melee attack time + STAT_MAX_HEALTH, // health / armor limit, changable by handicap + STAT_UNUSED_INDEX12, // unused stat index (don't remove if you want to keep demo networking!) + STAT_UNUSED_INDEX13, // BFP - Beam firing weapon state + STAT_UNUSED_INDEX14, // BFP - Force field weapon state + //STAT_UNUSED_INDEX15 // BFP - Fly tilt angles (left: moves to -80, right: moves to 80), rename during the config implementation + STAT_HITSTUN_TIME // BFP - Hit stun time +} statIndex_t; // player_state->persistant[] indexes // these fields are the only part of player_state that isn't @@ -236,61 +289,91 @@ typedef enum { PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked PERS_KILLED, // count of the number of times you died // player awards tracking - PERS_IMPRESSIVE_COUNT, // two railgun hits in a row PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time - PERS_DEFEND_COUNT, // defend awards - PERS_ASSIST_COUNT, // assist awards - PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet - PERS_CAPTURES // captures -} persEnum_t; + // BFP - NOTE: These indexes are being kept for original BFP networking + // BFP - No impressive, gauntlet, defend, assist and capture (PERS_CAPTURES is renamed) counters are used. Renamed as PERS_UNUSED_INDEX00 + PERS_UNUSED_INDEX10, //PERS_IMPRESSIVE_COUNT, // two railgun hits in a row + PERS_UNUSED_INDEX11, //PERS_DEFEND_COUNT, // defend awards + PERS_UNUSED_INDEX12, //PERS_ASSIST_COUNT, // assist awards + // BFP - This index is used in original BFP networking, but remains unknown + PERS_UNUSED_INDEX13, //PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet + + PERS_POWERLEVEL, // BFP - Powerlevel (before Q3: //PERS_CAPTURES, // captures) + + PERS_UNUSED_INDEX15 // BFP - ??? (Original BFP networking says it appears when spawning at the first time of all in-game) +} persEnum_t; +// BFP - If you want to keep demo networking, change the way to use the EF_ flags // entityState_t->eFlags #define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD #define EF_AURA 0x00000002 // BFP - Aura, used to display players' aura #define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes #define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite #define EF_PLAYER_EVENT 0x00000010 -#define EF_BOUNCE 0x00000010 // for missiles -#define EF_BOUNCE_HALF 0x00000020 // for missiles -#define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite +// BFP - Unused EF_BOUNCE and EF_BOUNCE_HALF +//#define EF_BOUNCE 0x00000010 // for missiles +// BFP - This eFlag hack is used for client/player only: +#define EF_READY_KI_ATTACK 0x00000010 // BFP - Ready ki attack +//#define EF_BOUNCE_HALF 0x00000020 // for missiles +// BFP - EF_AWARD_GAUNTLET flag is renamed // draw a gauntlet sprite +#define EF_FLIGHT 0x00000040 // BFP - Used for flying status #define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) #define EF_FIRING 0x00000100 // for lightning gun -#define EF_KAMIKAZE 0x00000200 +#define EF_KI_BOOST 0x00000200 // BFP - Used for ki boost status #define EF_MOVER_STOP 0x00000400 // will push otherwise -#define EF_AWARD_CAP 0x00000800 // draw the capture sprite +// BFP - This eFlag hack is used for client/player only: +#define EF_AURA_TIER_UP 0x00000400 // BFP - Aura tier up effect when transforms or passes to the next tier +// BFP - EF_AWARD_CAP is unused +//#define EF_AWARD_CAP 0x00000800 // draw the capture sprite #define EF_TALK 0x00001000 // draw a talk balloon #define EF_CONNECTION 0x00002000 // draw a connection trouble sprite #define EF_VOTED 0x00004000 // already cast a vote -#define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite -#define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite -#define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite -#define EF_AWARD_DENIED 0x00040000 // denied +// BFP - EF_AWARD_IMPRESSIVE flag is renamed // draw an impressive sprite +#define EF_MONSTER 0x00008000 // BFP - Player marked as monster on monster gametype (g_gametype 4) +// BFP - No EF_AWARD_DEFEND and EF_AWARD_ASSIST flags +// #define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite +// #define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite +// BFP - Unused EF flag +// #define EF_AWARD_DENIED 0x00040000 // denied #define EF_TEAMVOTED 0x00080000 // already cast a team vote -// BFP - NOTE: Should use PW_* instead using pm_flags? -// That goes for PW_HASTE and PW_FLIGHT, instead using PMF_KI_BOOST and PMF_FLYING +// BFP - We can use generic1 integer 8-bit fields, but if we use as integer, only can use until 128 ~ 256 +// used for ki charge points +// entityState_t->generic1 +// #define GENF_FLAG_1 1 +// #define GENF_FLAG_2 2 +// #define GENF_FLAG_3 4 +// #define GENF_FLAG_4 8 +// #define GENF_FLAG_5 16 +// #define GENF_FLAG_6 32 +// #define GENF_FLAG_7 64 +// #define GENF_FLAG_8 128 // NOTE: may not have more than 16 typedef enum { + // BFP - NOTE: Modified according to keep original BFP networking + // PW_REGEN doesn't appear and has been replaced by other feature + // BFP - that's where PW_HASTE, PW_BATTLESUIT and PW_INVIS are marked as PW_NONE after picking up PW_NONE, PW_QUAD, - PW_BATTLESUIT, - PW_HASTE, - PW_INVIS, - PW_REGEN, - PW_FLIGHT, - - PW_REDFLAG, - PW_BLUEFLAG, - PW_NEUTRALFLAG, - - PW_SCOUT, - PW_GUARD, - PW_DOUBLER, - PW_AMMOREGEN, - PW_INVULNERABILITY, + + // BFP - Red and blue flag are replaced from PW_BATTLESUIT and PW_HASTE according to original BFP networking + PW_REDFLAG, //PW_BATTLESUIT, + PW_BLUEFLAG, //PW_HASTE, + + PW_UNUSED_INDEX4, // BFP - Unused powerup index //PW_INVIS + PW_UNUSED_INDEX5, // BFP - That would be PW_FLIGHT, used for flying + PW_UNUSED_INDEX6, // BFP - Used for ki recharge + PW_UNUSED_INDEX7, // BFP - Used for ki use/boost + PW_UNUSED_INDEX8, // BFP - Used for blocking seconds (defend from melee, beams, explosions and impacts, and reflect ki attack projectiles) + PW_UNUSED_INDEX9, // BFP - Used for melee toggle + PW_UNUSED_INDEX10, // BFP - Used for hit stun seconds + PW_UNUSED_INDEX11, // BFP - Used for ki attack charge points + PW_UNUSED_INDEX12, // BFP - Used for enable/disable monster in monster gamemode (g_gametype 4) + PW_UNUSED_INDEX13, // BFP - Used for beam firing state + PW_UNUSED_INDEX14, // BFP - Used for jump (looks strange...) PW_NUM_POWERUPS @@ -301,27 +384,31 @@ typedef enum { HI_TELEPORTER, HI_MEDKIT, - HI_KAMIKAZE, - HI_PORTAL, - HI_INVULNERABILITY, HI_NUM_HOLDABLE } holdable_t; +// BFP - NOTE: According to keep original BFP networking, these are modified for features typedef enum { - WP_NONE, - - WP_GAUNTLET, - WP_MACHINEGUN, - WP_SHOTGUN, - WP_GRENADE_LAUNCHER, - WP_ROCKET_LAUNCHER, - WP_LIGHTNING, - WP_RAILGUN, - WP_PLASMAGUN, - WP_BFG, - WP_GRAPPLING_HOOK, + WP_NONE, // BFP - TODO: First attack selected, rename during the config implementation + WP_GAUNTLET, // BFP - TODO: Second attack selected, rename during the config implementation + WP_MACHINEGUN, // BFP - TODO: Third attack selected, rename during the config implementation + WP_SHOTGUN, // BFP - TODO: Fourth attack selected, rename during the config implementation + WP_GRENADE_LAUNCHER, // BFP - TODO: Fifth (or last) attack selected and also for a timer of 2000 msec when being attacked/damaged, rename during the config implementation + + WP_ROCKET_LAUNCHER, // BFP - Original demo networking: Ki recharge delay time (for g_chargeDelay), rename during the config implementation + WP_LIGHTNING, // BFP - Original demo networking: Hit stun delay after receiving hit stun, rename during the config implementation + WP_RAILGUN, // BFP - Original demo networking: Block delay, rename during the config implementation + WP_PLASMAGUN, // BFP - Original demo networking: Ki use/boost toggle, rename during the config implementation + WP_BFG, // BFP - Original demo networking: Flight toggle key control, rename during the config implementation + WP_GRAPPLING_HOOK, // BFP - Original demo networking: Blind seconds, rename during the config implementation + +// WP_UNUSED_INDEX11, // BFP - Original demo networking: Rapid attacks like ki storm (alternates -1 and 1), rename during the config implementation +// WP_UNUSED_INDEX12, // BFP - Original demo networking: Unknown or unused index +// WP_UNUSED_INDEX13, // BFP - Original demo networking: Toggle to use Short-Range Teleport - Zanzoken, rename during the config implementation +// WP_UNUSED_INDEX14, // BFP - Original demo networking: Directional left/right keys to move left/right while pressing, adds time msec, looks like a timer to handle for Zanzoken, rename during the config implementation +// WP_UNUSED_INDEX15, // BFP - Original demo networking: Enables/disables beam struggle , rename during the config implementation WP_NUM_WEAPONS } weapon_t; @@ -348,98 +435,10 @@ typedef enum { #define EVENT_VALID_MSEC 300 typedef enum { - EV_NONE, - - EV_FOOTSTEP, - EV_FOOTSTEP_METAL, - EV_FOOTSPLASH, - EV_FOOTWADE, - EV_SWIM, - - EV_STEP_4, - EV_STEP_8, - EV_STEP_12, - EV_STEP_16, - - EV_FALL_SHORT, - EV_FALL_MEDIUM, - EV_FALL_FAR, - - EV_JUMP_PAD, // boing sound at origin, jump sound on player - - EV_JUMP, - EV_WATER_TOUCH, // foot touches - EV_WATER_LEAVE, // foot leaves - EV_WATER_UNDER, // head touches - EV_WATER_CLEAR, // head leaves - - EV_ITEM_PICKUP, // normal item pickups are predictable - EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone - - EV_NOAMMO, - EV_CHANGE_WEAPON, - EV_FIRE_WEAPON, - - EV_USE_ITEM0, - EV_USE_ITEM1, - EV_USE_ITEM2, - EV_USE_ITEM3, - EV_USE_ITEM4, - EV_USE_ITEM5, - EV_USE_ITEM6, - EV_USE_ITEM7, - EV_USE_ITEM8, - EV_USE_ITEM9, - EV_USE_ITEM10, - EV_USE_ITEM11, - EV_USE_ITEM12, - EV_USE_ITEM13, - EV_USE_ITEM14, - EV_USE_ITEM15, - - EV_ITEM_RESPAWN, - EV_ITEM_POP, - EV_PLAYER_TELEPORT_IN, - EV_PLAYER_TELEPORT_OUT, - - EV_GRENADE_BOUNCE, // eventParm will be the soundindex - - EV_GENERAL_SOUND, - EV_GLOBAL_SOUND, // no attenuation - EV_GLOBAL_TEAM_SOUND, - - EV_BULLET_HIT_FLESH, - EV_BULLET_HIT_WALL, - - EV_MISSILE_HIT, - EV_MISSILE_MISS, - EV_MISSILE_MISS_METAL, - EV_RAILTRAIL, - EV_SHOTGUN, - EV_BULLET, // otherEntity is the shooter - - EV_PAIN, - EV_DEATH1, - EV_DEATH2, - EV_DEATH3, - EV_OBITUARY, - - EV_POWERUP_QUAD, - EV_POWERUP_BATTLESUIT, - EV_POWERUP_REGEN, - - EV_GIB_PLAYER, // gib a previously living player - EV_SCOREPLUM, // score plum - - EV_DEBUG_LINE, - EV_STOPLOOPINGSOUND, - EV_TAUNT, - EV_TAUNT_YES, - EV_TAUNT_NO, - EV_TAUNT_FOLLOWME, - EV_TAUNT_GETFLAG, - EV_TAUNT_GUARDBASE, - EV_TAUNT_PATROL + // BFP - Events are declared in bg_events.h file +#define EVENT_ENUMS + #include "bg_events.h" +#undef EVENT_ENUMS } entity_event_t; @@ -451,14 +450,11 @@ typedef enum { GTS_BLUE_RETURN, GTS_RED_TAKEN, GTS_BLUE_TAKEN, - GTS_REDOBELISK_ATTACKED, - GTS_BLUEOBELISK_ATTACKED, GTS_REDTEAM_SCORED, GTS_BLUETEAM_SCORED, GTS_REDTEAM_TOOK_LEAD, GTS_BLUETEAM_TOOK_LEAD, - GTS_TEAMS_ARE_TIED, - GTS_KAMIKAZE + GTS_TEAMS_ARE_TIED } global_team_sound_t; // animations @@ -540,8 +536,7 @@ typedef enum { TORSO_ATTACK4_STRIKE, // BFP // BFP - The following attackset animations are just reminders, these aren't used as variables - - /* +/* // BFP - (point finger right hand) TORSO_ATTACK5_PREPARE, // BFP TORSO_ATTACK5_STRIKE, // BFP @@ -589,7 +584,7 @@ typedef enum { // BFP - (ken and ryu fireball) TORSO_ATTACK16_PREPARE, // BFP TORSO_ATTACK16_STRIKE, // BFP - */ +*/ // BFP - The following animations are useless, // possibly can be removed only if the game works as should @@ -600,7 +595,7 @@ typedef enum { TORSO_AFFIRMATIVE, TORSO_NEGATIVE, - MAX_ANIMATIONS, // BFP - important variable, don't remove! + MAX_ANIMATIONS, // BFP - important index, don't remove! // LEGS_BACKCR, // BFP - No longer used // LEGS_BACKWALK, // BFP - No longer used @@ -608,7 +603,7 @@ typedef enum { FLAG_STAND, FLAG_STAND2RUN, - MAX_TOTALANIMATIONS // BFP - important variable, don't remove! + MAX_TOTALANIMATIONS // BFP - important index, don't remove! } animNumber_t; typedef struct animation_s { @@ -656,30 +651,10 @@ typedef enum { // means of death typedef enum { - MOD_UNKNOWN, - MOD_SHOTGUN, - MOD_GAUNTLET, - MOD_MACHINEGUN, - MOD_GRENADE, - MOD_GRENADE_SPLASH, - MOD_ROCKET, - MOD_ROCKET_SPLASH, - MOD_PLASMA, - MOD_PLASMA_SPLASH, - MOD_RAILGUN, - MOD_LIGHTNING, - MOD_BFG, - MOD_BFG_SPLASH, - MOD_WATER, - MOD_SLIME, - MOD_LAVA, - MOD_CRUSH, - MOD_TELEFRAG, - MOD_FALLING, - MOD_SUICIDE, - MOD_TARGET_LASER, - MOD_TRIGGER_HURT, - MOD_GRAPPLE + // BFP - Means of death are declared in bg_meansofdeath.h file +#define MOD_ENUMS + #include "bg_meansofdeath.h" +#undef MOD_ENUMS } meansOfDeath_t; @@ -708,6 +683,7 @@ typedef struct gitem_s { char *world_model[MAX_ITEM_MODELS]; char *icon; + // BFP - TODO: Use as attackName for skin config char *pickup_name; // for printing on pickup int quantity; // for ammo how much, or duration of powerup @@ -733,7 +709,7 @@ qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const play // g_dmflags->integer flags -// #define DF_NO_FALLING 8 // BFP - Disabled, there's no fall damage on BFP +#define DF_NO_FALLING 8 #define DF_FIXED_FOV 16 #define DF_NO_FOOTSTEPS 32 @@ -776,7 +752,7 @@ typedef enum { void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); -void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); +void BG_AddPredictableEventToPlayerstate( entity_event_t newEvent, int eventParm, playerState_t *ps, int entityNum ); void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); @@ -794,25 +770,8 @@ qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTim #define MAX_BOTS_TEXT 8192 -// Kamikaze - -// 1st shockwave times -#define KAMI_SHOCKWAVE_STARTTIME 0 -#define KAMI_SHOCKWAVEFADE_STARTTIME 1500 -#define KAMI_SHOCKWAVE_ENDTIME 2000 -// explosion/implosion times -#define KAMI_EXPLODE_STARTTIME 250 -#define KAMI_IMPLODE_STARTTIME 2000 -#define KAMI_IMPLODE_ENDTIME 2250 -// 2nd shockwave times -#define KAMI_SHOCKWAVE2_STARTTIME 2000 -#define KAMI_SHOCKWAVE2FADE_STARTTIME 2500 -#define KAMI_SHOCKWAVE2_ENDTIME 3000 -// radius of the models without scaling -#define KAMI_SHOCKWAVEMODEL_RADIUS 88 -#define KAMI_BOOMSPHEREMODEL_RADIUS 72 -// maximum radius of the models during the effect -#define KAMI_SHOCKWAVE_MAXRADIUS 1320 -#define KAMI_BOOMSPHERE_MAXRADIUS 720 -#define KAMI_SHOCKWAVE2_MAXRADIUS 704 - +// calculated by modulus.c for appropriate dividers: +#define TMOD_004 4272943 +#define TMOD_075 2292106 +#define TMOD_1000 5730265 +#define TMOD_2000 5730265 diff --git a/source/game/bg_slidemove.c b/source/game/bg_slidemove.c index 6c957a5..3f4151d 100644 --- a/source/game/bg_slidemove.c +++ b/source/game/bg_slidemove.c @@ -232,7 +232,10 @@ PM_StepSlideMove */ void PM_StepSlideMove( qboolean gravity ) { vec3_t start_o, start_v; +#define SLIDE_DOWN_VECTOR 0 +#if SLIDE_DOWN_VECTOR vec3_t down_o, down_v; +#endif trace_t trace; // float down_dist, up_dist; // vec3_t delta, delta2; @@ -256,8 +259,10 @@ void PM_StepSlideMove( qboolean gravity ) { return; } +#if SLIDE_DOWN_VECTOR VectorCopy (pm->ps->origin, down_o); VectorCopy (pm->ps->velocity, down_v); +#endif VectorCopy (start_o, up); up[2] += STEPSIZE; @@ -289,7 +294,7 @@ void PM_StepSlideMove( qboolean gravity ) { PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); } -#if 0 +#if SLIDE_DOWN_VECTOR // 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 ) { diff --git a/source/game/g_active.c b/source/game/g_active.c index a5faf7a..47218f8 100644 --- a/source/game/g_active.c +++ b/source/game/g_active.c @@ -97,18 +97,19 @@ Check for lava / slime contents and drowning ============= */ void P_WorldEffects( gentity_t *ent ) { - qboolean envirosuit; int waterlevel; + // BFP - No battlesuit powerup + //qboolean envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; + waterlevel = ent->waterlevel; + + // BFP - No drowning +#if 0 if ( ent->client->noclip ) { ent->client->airOutTime = level.time + 12000; // don't need air return; } - waterlevel = ent->waterlevel; - - envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; - // // check for drowning // @@ -148,6 +149,7 @@ void P_WorldEffects( gentity_t *ent ) { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } +#endif // // check for sizzle damage (move to pmove?) @@ -157,9 +159,13 @@ void P_WorldEffects( gentity_t *ent ) { if (ent->health > 0 && ent->pain_debounce_time <= level.time ) { + // BFP - No battlesuit powerup +#if 0 if ( envirosuit ) { G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); - } else { + } else +#endif + { if (ent->watertype & CONTENTS_LAVA) { G_Damage (ent, NULL, NULL, NULL, NULL, 30*waterlevel, 0, MOD_LAVA); @@ -242,7 +248,8 @@ void G_TouchTriggers( gentity_t *ent ) { gentity_t *hit; trace_t trace; vec3_t mins, maxs; - static vec3_t range = { 40, 40, 52 }; + // BFP - Disabled + //static vec3_t range = { 40, 40, 52 }; if ( !ent->client ) { return; @@ -253,14 +260,16 @@ void G_TouchTriggers( gentity_t *ent ) { return; } - VectorSubtract( ent->client->ps.origin, range, mins ); - VectorAdd( ent->client->ps.origin, range, maxs ); - - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + // BFP - Disabled, to touch correctly without a defined range + // and num declaration moved down mins and maxs. + // Player monster cannot touch and that causes troubles by stucking from there + //VectorSubtract( ent->client->ps.origin, range, mins ); + //VectorAdd( ent->client->ps.origin, range, maxs ); // 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 ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for ( i=0 ; is.eType == ET_ITEM ) { + // BFP - Most players and player monster can pick up items with its bounding box + if ( mins[0] > hit->r.absmax[0] || maxs[0] < hit->r.absmin[0] + || mins[1] > hit->r.absmax[1] || maxs[1] < hit->r.absmin[1] + || mins[2] > hit->r.absmax[2] || maxs[2] < hit->r.absmin[2] ) { + continue; + } + // BFP - Disabled to make bounding box detection work +#if 0 if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { continue; } +#endif } else { if ( !trap_EntityContact( mins, maxs, hit ) ) { continue; @@ -396,16 +414,60 @@ Actions that happen once a second */ void ClientTimerActions( gentity_t *ent, int msec ) { gclient_t *client; + // BFP - Flight cost total variable + float flightCostTotal = g_flightCost.value + g_flightCostPct.value * ( ent->client->ps.stats[STAT_MAX_KI] * 0.01 ); + // BFP - Random factor for ki charge last digits + float rndKiCharge = 0.8f + crandom() * 0.2f; client = ent->client; client->timeResidual += msec; + // BFP - Ki boost consumption + if ( ( ( client->pers.cmd.buttons & BUTTON_KI_USE ) || ( client->ps.eFlags & EF_KI_BOOST ) ) + && client->ps.stats[STAT_KI] > 0 + && client->ps.stats[STAT_HITSTUN_TIME] <= 0 + && !( client->ps.pm_flags & PMF_BLOCK ) + && client->ps.weaponstate != WEAPON_STUN ) { + // BFP - NOTE: On original BFP, this is handled into another way, so, the formula remains unknown, it tried the best + float boostCostTotal = ( g_boostCost.value * 0.001 ) + ( g_boostCostPct.value * 0.1 ) * client->ps.stats[STAT_MAX_KI] * 0.0001; + // use msec to adjust the consumption + client->kiResidual += boostCostTotal * msec; + if ( client->kiResidual >= 1.0f ) { + int drop = (int)client->kiResidual; + client->ps.stats[STAT_KI] -= drop; + client->kiResidual -= drop; + } + } + + // BFP - Charge ki + if ( ( client->ps.pm_flags & PMF_KI_CHARGE ) && ( client->ps.eFlags & EF_AURA ) + && client->ps.stats[STAT_HITSTUN_TIME] <= 0 + && client->ps.stats[STAT_KI] < client->ps.stats[STAT_MAX_KI] ) { + float kiChargeTotal = ( g_kiCharge.value * 0.01 ) + g_kiChargePct.value * ( client->ps.stats[STAT_MAX_KI] * 0.0001 ); + client->ps.stats[STAT_KI] += kiChargeTotal * rndKiCharge; + } + + // BFP - Block ki consume + if ( ( client->ps.pm_flags & PMF_BLOCK ) + && client->ps.stats[STAT_KI] > 0 + && client->blockKnockbackTime <= 0 + && random() < 0.75 ) { // a weird random thingy (¬_¬') tried to get the similar result + // BFP - NOTE: On original BFP, this is handled into another way, so, the formula remains unknown, it tried the best + float blockCostTotal = ( g_blockCost.value * 0.01 ) + ( g_blockCostPct.value * 0.01 ) * ( client->ps.stats[STAT_MAX_KI] * 0.0001 ); + if ( blockCostTotal < 1 ) { + blockCostTotal = 1; + } + client->ps.stats[STAT_KI] -= blockCostTotal; + } + while ( client->timeResidual >= 1000 ) { client->timeResidual -= 1000; + // BFP - No regen powerup +#if 0 // regenerate if ( client->ps.powerups[PW_REGEN] ) { - if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { + 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; @@ -418,33 +480,31 @@ void ClientTimerActions( gentity_t *ent, int msec ) { } G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } - } else { + } else +#endif + { // count down health when over max if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { ent->health--; } } - // BFP - Ki up/down when flying/ki use - if ( client->ps.pm_flags & PMF_FLYING ) { - - // BFP - TODO: Add cvar for flight cost - - if ( client->ps.stats[STAT_KI] > 0 ) { - client->ps.stats[STAT_KI]--; - - if ( client->pers.cmd.buttons & BUTTON_KI_USE ) - client->ps.stats[STAT_KI]--; + // BFP - Decrease ki when flying + if ( ( client->ps.eFlags & EF_FLIGHT ) + && client->ps.stats[STAT_KI] > 0 + && !( client->ps.pm_flags & PMF_KI_CHARGE ) ) { // don't decrease when charging + if ( g_flightCostPct.value > 0 && client->ps.persistant[PERS_POWERLEVEL] < 1000 ) { // reduce a bit if the percentage cost is more than 0 and has less powerlevel + --flightCostTotal; } - } else { - client->ps.stats[STAT_KI]++; + client->ps.stats[STAT_KI] -= flightCostTotal; } - // BFP - if ki drops to 0, disable flight - if ( client->ps.stats[STAT_KI] <= 0 ) { - client->ps.stats[STAT_KI] = 0; - client->ps.pm_flags &= ~PMF_FLYING; - // Com_Printf( "ki amount: %d\n", client->ps.stats[STAT_KI] ); + // BFP - Regenerate ki + if ( !( client->pers.cmd.buttons & BUTTON_KI_USE ) && !( client->ps.eFlags & EF_KI_BOOST ) + && !( ( client->ps.pm_flags & PMF_KI_CHARGE ) && ( client->ps.eFlags & EF_AURA ) ) // don't increase when charging + && !( ( client->ps.pm_flags & PMF_KI_CHARGE ) && ( client->ps.eFlags & EF_AURA ) && client->ps.stats[STAT_HITSTUN_TIME] > 0 ) // don't increase when trying to charge when stunned + && !( ( client->pers.cmd.buttons & BUTTON_ATTACK ) && client->ps.stats[STAT_HITSTUN_TIME] > 0 ) ) { // don't increase when trying to attack when stunned + client->ps.stats[STAT_KI] += g_kiRegen.value + ( g_kiRegenPct.value * client->ps.stats[STAT_MAX_KI] * 0.01 ); } // count down armor when over max @@ -452,6 +512,31 @@ void ClientTimerActions( gentity_t *ent, int msec ) { client->ps.stats[STAT_ARMOR]--; } } + + // BFP - Set maximum ki + if ( client->ps.stats[STAT_KI] > client->ps.stats[STAT_MAX_KI] ) { + client->ps.stats[STAT_KI] = client->ps.stats[STAT_MAX_KI]; + } + + // BFP - If ki drops to 0, disable flight + if ( client->ps.stats[STAT_KI] <= 0 ) { + client->ps.stats[STAT_KI] = 0; + client->ps.eFlags &= ~EF_FLIGHT; + } + + // BFP - When the player doesn't have more ki, gets a hit stun + if ( ( client->ps.stats[STAT_KI] <= 0 ) + || ( ( client->ps.eFlags & EF_FLIGHT ) && client->ps.stats[STAT_KI] < flightCostTotal && client->ps.stats[STAT_HITSTUN_TIME] <= 0 ) + || ( ( client->pers.cmd.buttons & BUTTON_ATTACK ) && client->ps.stats[STAT_HITSTUN_TIME] > 0 ) ) { + if ( ( client->ps.eFlags & EF_FLIGHT ) && client->ps.stats[STAT_KI] < flightCostTotal && client->ps.stats[STAT_HITSTUN_TIME] <= 0 ) { + client->ps.stats[STAT_KI] /= flightCostTotal; + } + if ( client->ps.stats[STAT_KI] <= 0 ) { + client->ps.stats[STAT_HITSTUN_TIME] = 100; // handle time + } else { + client->ps.stats[STAT_HITSTUN_TIME] = 1000; + } + } } /* @@ -472,6 +557,8 @@ void ClientIntermissionThink( gclient_t *client ) { // this used to be an ^1 but once a player says ready, it should stick client->readyToExit = 1; } + + client->ps.commandTime = client->pers.cmd.serverTime; } @@ -488,7 +575,6 @@ void ClientEvents( gentity_t *ent, int oldEventSequence ) { int event; gclient_t *client; int damage; - vec3_t dir; vec3_t origin, angles; // qboolean fired; gitem_t *item; @@ -506,27 +592,34 @@ void ClientEvents( gentity_t *ent, int oldEventSequence ) { case EV_FALL_MEDIUM: case EV_FALL_FAR: // BFP - There's no crash land damage when the players fell in the ground - /* if ( ent->s.eType != ET_PLAYER ) { break; // not in the player model } if ( g_dmflags.integer & DF_NO_FALLING ) { break; } - if ( event == EV_FALL_FAR ) { - damage = 10; - } else { - damage = 5; + // BFP - When the player is falling with stunned status + // if powerlevel is very low, weaker at falling (player has less max health) + // but when the powerlevel is getting higher, the less damage it will have (player has more max health) + if ( client->ps.stats[STAT_HITSTUN_TIME] > 0 ) { + damage = 5; // medium fall + if ( event == EV_FALL_FAR ) { + damage = 10; + } + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage ( ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING ); } - VectorSet (dir, 0, 0, 1); - ent->pain_debounce_time = level.time + 200; // no normal pain sound - G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); - */ break; case EV_FIRE_WEAPON: + { + // BFP - Multishot test + if ( client->ps.weapon == WP_GRENADE_LAUNCHER ) { + ent->multishot = 3; + } FireWeapon( ent ); break; + } case EV_USE_ITEM1: // teleporter // drop flags in CTF @@ -539,9 +632,6 @@ void ClientEvents( gentity_t *ent, int oldEventSequence ) { } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { item = BG_FindItemForPowerup( PW_BLUEFLAG ); j = PW_BLUEFLAG; - } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - j = PW_NEUTRALFLAG; } if ( item ) { @@ -560,7 +650,7 @@ void ClientEvents( gentity_t *ent, int oldEventSequence ) { break; case EV_USE_ITEM2: // medkit - ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; + ent->health = client->ps.stats[STAT_MAX_HEALTH]; // BFP - Before Q3: + 25 break; @@ -610,20 +700,252 @@ void SendPendingPredictableEvents( playerState_t *ps ) { /* ================= -FlyingThink +BlockHandling ================= */ -void FlyingThink( gentity_t *ent, usercmd_t *ucmd ) { // BFP - Flight +static void BlockHandling( gclient_t *client, usercmd_t *ucmd ) { // BFP - Block, reflect ki attacks and reduce health damage + // if the block length duration hasn't been expired yet and + // pressing ki charge (if the aura is lighting) or attack buttons, then stop blocking and start the delay + if ( ( client->ps.pm_flags & PMF_BLOCK ) + && ( ( client->ps.pm_flags & PMF_KI_CHARGE ) + || ( ucmd->buttons & BUTTON_KI_CHARGE ) + || ( ucmd->buttons & BUTTON_ATTACK ) + || ( ucmd->buttons & BUTTON_MELEE ) ) ) { + client->ps.pm_flags &= ~PMF_BLOCK; + client->blockTime = 0; + client->blockDelayTime = level.time + (g_blockDelay.integer * 1000); + } + + // initialize the blocking and start the block length duration, specifically, ki boost and aura are disabled + if ( !( client->ps.pm_flags & PMF_BLOCK ) + && ( ucmd->buttons & BUTTON_BLOCK ) + && !( ucmd->buttons & BUTTON_KI_CHARGE ) + && client->blockTime <= 0 + && client->blockDelayTime <= 0 ) { + client->ps.pm_flags |= PMF_BLOCK; + if ( ucmd->buttons & BUTTON_KI_USE ) { + client->ps.eFlags &= ~EF_KI_BOOST; + client->ps.eFlags &= ~EF_AURA; + } + client->blockTime = level.time + (g_blockLength.integer * 1000); + } + + // when the block length duration has been expired, then start the delay to avoid user + if ( ( client->ps.pm_flags & PMF_BLOCK ) + && client->blockTime > 0 + && level.time >= client->blockTime ) { + client->ps.pm_flags &= ~PMF_BLOCK; + client->blockTime = 0; + client->blockDelayTime = level.time + (g_blockDelay.integer * 1000); + } + + // knockback when reflecting from attacks + if ( ( client->ps.pm_flags & PMF_BLOCK ) + && client->blockTime > 0 + && level.time >= client->blockKnockbackTime ) { + client->blockKnockbackTime = 0; + } + + // debug print block length and delay duration +#if 0 + Com_Printf( "BLOCK LENGTH: %d\n", client->blockTime ); + Com_Printf( "BLOCK DELAY: %d\n", client->blockDelayTime ); +#endif + + // handle the delay and don't leave the user get away with it + if ( !( client->ps.pm_flags & PMF_BLOCK ) + && client->blockDelayTime > 0 + && level.time < client->blockDelayTime ) { + client->blockTime = 0; + ucmd->buttons &= ~BUTTON_BLOCK; // if the user holds the key, when that ends, then immediately enters to this status again + } + + // reset block delay time if expired + if ( !( client->ps.pm_flags & PMF_BLOCK ) + && client->blockDelayTime > 0 + && level.time >= client->blockDelayTime ) { + client->blockDelayTime = 0; + } +} + +/* +================= +MeleeHandling +================= +*/ +static void MeleeHandling( gentity_t *ent, usercmd_t *ucmd, pmove_t *pm ) { // BFP - Melee gclient_t *client; client = ent->client; - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; + if ( client->ps.stats[STAT_HITSTUN_TIME] > 0 ) { + return; + } - // enableflight button cycles - if ( ( client->buttons & BUTTON_ENABLEFLIGHT ) && ! ( client->oldbuttons & BUTTON_ENABLEFLIGHT ) ) { - Cmd_BFP_Fly_f( ent ); + if ( !( ucmd->buttons & BUTTON_MELEE ) ) { + client->ps.pm_flags &= ~PMF_MELEE; + } + + if ( !( ucmd->buttons & BUTTON_TALK ) && ( ucmd->buttons & BUTTON_MELEE ) + && !( client->ps.pm_flags & PMF_KI_CHARGE ) + && client->ps.weaponTime <= 0 ) { + pm->meleeHit = CheckMeleeAttack( ent ); + } +} + +/* +============ +Zanzoken +============ +*/ +qboolean Zanzoken( gentity_t *ent, int range ) { // BFP - Short-Range Teleport (Zanzoken) + trace_t tr; + vec3_t right, up, start, direction; + int startRightRange = ( range < 0 ) ? -10 : 10; + + // set diagonal direction, included the up vector for upward detection + AngleVectors( ent->client->ps.viewangles, NULL, right, up ); + + // upward detection, avoid if the player is touching the surface above + VectorMA( ent->client->ps.origin, 25, up, start ); + VectorMA( start, 100, up, direction ); + + trap_Trace( &tr, start, ent->r.mins, ent->r.maxs, direction, ent->s.number, MASK_PLAYERSOLID ); + if ( tr.startsolid || tr.allsolid ) { + return qfalse; + } + + // if there's something solid diagonally, then avoid the teleportation + VectorMA( ent->client->ps.origin, startRightRange, right, start ); + VectorMA( ent->client->ps.origin, range, right, direction ); + + trap_Trace( &tr, start, ent->r.mins, ent->r.maxs, direction, ent->s.number, MASK_PLAYERSOLID ); + if ( tr.startsolid || tr.allsolid ) { + return qfalse; + } + + // TELEPORT! + tr.endpos[2] += 16; // place the position a bit up + VectorCopy( tr.endpos, ent->client->ps.origin ); + + // sound event + BG_AddPredictableEventToPlayerstate( EV_ZANZOKEN_IN, 0, &ent->client->ps, -1 ); + + return qtrue; +} + +/* +================= +ZanzokenHandling +================= +*/ +static void ZanzokenHandling( gentity_t *ent, usercmd_t *ucmd ) { // BFP - Handling short-range teleport + const int ZANZOKEN_NUMBER_TIMES_ALLOWED = 10, + ZANZOKEN_ABUSE_DELAY = 2000, + MAX_ZANZOKEN_PRESS_TIME = 240, + ZANZOKEN_COOLDOWN = 70; + gclient_t *client = ent->client; + int currentTime = level.time; // use level.time which is not affected by timescale + + if ( client->ps.weaponstate == WEAPON_ACTIVE + || client->ps.weaponstate == WEAPON_BEAMSTRUGGLE + || client->ps.weaponstate == WEAPON_STUN ) { + return; + } + + // check if zanzoken is on cooldown + if ( client->zanzokenLastUsed > 0 + && currentTime - client->zanzokenLastUsed < ZANZOKEN_COOLDOWN ) { + return; + } + + if ( client->ps.pm_flags & PMF_ULTIMATE_TIER ) { + return; + } + + // zanzoken cannot be used with ki charging status + if ( client->ps.pm_flags & PMF_KI_CHARGE ) { + return; + } + + // restriction: stop abusing zanzoken technique all time + if ( client->zanzokenNumberTimesAllowed >= ZANZOKEN_NUMBER_TIMES_ALLOWED ) { + client->zanzokenNumberTimesAllowed = 0; + client->zanzokenDelay = currentTime; + return; + } + + if ( client->zanzokenDelay > 0 && currentTime - client->zanzokenDelay <= ZANZOKEN_ABUSE_DELAY ) { + return; + } + + if ( ucmd->rightmove && client->zanzokenPressTime <= 0 ) { + client->zanzokenPressTime = currentTime; + client->zanzokenNow = qfalse; + // handle directions to avoid pressing the opposite + if ( ucmd->rightmove > 0 ) { + client->zanzokenLeft = qfalse; + client->zanzokenRight = qtrue; + } else { + client->zanzokenLeft = qtrue; + client->zanzokenRight = qfalse; + } + } + + // once pressed and having one moment to press again, zanzoken will be possible at these milliseconds + if ( !ucmd->rightmove && client->zanzokenPressTime > 0 ) { + int elapsed = currentTime - client->zanzokenPressTime; + + if ( elapsed > 50 && elapsed <= MAX_ZANZOKEN_PRESS_TIME && !client->zanzokenNow ) { + client->zanzokenNow = qtrue; + client->zanzokenNumberTimesAllowed++; + } + + if ( elapsed > MAX_ZANZOKEN_PRESS_TIME ) { + client->zanzokenNumberTimesAllowed = 0; + client->zanzokenPressTime = 0; + client->zanzokenNow = qfalse; + client->zanzokenLeft = qfalse; + client->zanzokenRight = qfalse; + return; + } + } + + if ( client->ps.stats[STAT_KI] > ( client->ps.stats[STAT_MAX_KI] * 0.05 ) + && ucmd->rightmove && client->zanzokenNow ) { + int range = ( ucmd->rightmove > 0 ) ? 500 : -500; + + // handle the directions correctly + if ( ( ucmd->rightmove > 0 && !client->zanzokenRight ) + || ( ucmd->rightmove < 0 && !client->zanzokenLeft ) ) { + client->zanzokenLeft = qfalse; + client->zanzokenRight = qfalse; + return; + } + + // put in 0.1 msec delay before the player can 'zanzoken' out of stun + if ( client->ps.stats[STAT_HITSTUN_TIME] > 2900 ) { + client->zanzokenPressTime = 0; + client->zanzokenNow = qfalse; + client->zanzokenLeft = qfalse; + client->zanzokenRight = qfalse; + return; + } + + if ( Zanzoken( ent, range ) ) { + // block and stun statuses are removed when using zanzoken + if ( client->ps.stats[STAT_HITSTUN_TIME] <= 3000 ) { + client->ps.stats[STAT_HITSTUN_TIME] = 0; + } + client->ps.pm_flags &= ~PMF_BLOCK; + // consumes 5% of ki + client->ps.stats[STAT_KI] -= ( client->ps.stats[STAT_MAX_KI] * 0.05 ); + client->zanzokenPressTime = 0; + client->zanzokenNow = qfalse; + client->zanzokenLeft = qfalse; + client->zanzokenRight = qfalse; + client->zanzokenLastUsed = currentTime; // Set cooldown + } } } @@ -652,7 +974,7 @@ void ClientThink_real( gentity_t *ent ) { return; } // mark the time, so the connection sprite can be removed - ucmd = &ent->client->pers.cmd; + ucmd = &client->pers.cmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { @@ -711,7 +1033,8 @@ void ClientThink_real( gentity_t *ent ) { // clear the rewards if time if ( level.time > client->rewardTime ) { - client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); + // BFP - No impressive, gauntlet, defend, assist and cap medals + client->ps.eFlags &= ~(EF_AWARD_EXCELLENT /*| EF_AWARD_IMPRESSIVE | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP*/ ); } if ( client->noclip ) { @@ -727,54 +1050,104 @@ void ClientThink_real( gentity_t *ent ) { // set speed client->ps.speed = g_speed.value; - // BFP - Ki use has 2 options: "kiusetoggle" to toggle and "+button8" when key is being hold - // BFP - if BUTTON_KI_USE > speed - if ( ( ucmd->buttons & BUTTON_KI_USE ) // BFP - Using Ki - || ( ent->client->ps.pm_flags & PMF_KI_BOOST ) ) { // BFP - When "kiusetoggle" is binded, enables/disables - ent->client->ps.speed *= 2.5; - ent->client->ps.eFlags |= EF_AURA; - } else { - ent->client->ps.eFlags &= ~EF_AURA; - } - - // BFP - Ki Charge - if ( ucmd->buttons & BUTTON_KI_CHARGE ) { - client->ps.eFlags |= EF_AURA; - } - - if ( client->ps.pm_flags & PMF_FLYING ) { // BFP - Flight speed - client->ps.speed *= 1.5; - } - // BFP - TODO: When charging a ki attack like beam wave, consult FlyingThink and SpectatorThink if that's the case - - // BFP - if BUTTON_ENABLEFLIGHT enable flight - FlyingThink( ent, ucmd ); // prevents client-server side issues when there's other client in-game - -// BFP - no hook -#if 0 + // BFP - Remove beam // Let go of the hook if we aren't firing if ( client->ps.weapon == WP_GRAPPLING_HOOK && client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { - Weapon_HookFree(client->hook); + // Weapon_HookFree(client->hook); + Weapon_BFPBeamFree( client->hook ); } -#endif // set up for pmove oldEventSequence = client->ps.eventSequence; memset (&pm, 0, sizeof(pm)); - // check for the hit-scan gauntlet, don't let the action - // go through as an attack unless it actually hits something - if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && - ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { - pm.gauntletHit = CheckGauntletAttack( ent ); + if ( client->ps.pm_type != PM_DEAD && client->ps.pm_type != PM_SPECTATOR ) { + + // BFP - After unlocking tier, when the time is out, the aura tier effect vanishes + if ( level.time >= client->tierUnlockedTime && client->ps.persistant[PERS_POWERLEVEL] < 1000 ) { + client->ps.eFlags &= ~EF_AURA_TIER_UP; + } + + // BFP - Ultimate tier + if ( ( client->ps.pm_flags & PMF_ULTIMATE_TIER ) + && ( level.time >= client->tierUnlockedTime - 1000 ) ) { // remove aura transformation effect after 3.6 secs + client->ps.eFlags &= ~EF_AURA_TIER_UP; + } + + if ( ( client->ps.pm_flags & PMF_ULTIMATE_TIER ) && level.time >= client->tierUnlockedTime ) { + client->ps.pm_flags &= ~PMF_ULTIMATE_TIER; + } + + // BFP - Short-Range Teleport (Zanzoken) + ZanzokenHandling( ent, ucmd ); + + // BFP - Hit stun melee delay time + if ( client->hitStunMeleeDelayTime > 0 + && level.time >= client->hitStunMeleeDelayTime ) { + client->hitStunMeleeDelayTime = 0; + } + + // BFP - g_chargeDelay cvar for ki charge animation and appearing the aura after this time + if ( !( ucmd->buttons & BUTTON_KI_USE ) + && ( ucmd->buttons & BUTTON_KI_CHARGE ) + && !( client->ps.pm_flags & PMF_KI_CHARGE ) ) { + client->ps.pm_time = ( g_chargeDelay.integer > 0 ) ? g_chargeDelay.integer : 0; + } + + // BFP - Ki use has 2 options: "kiusetoggle" to toggle and "+button8" when key is being hold + if ( client->ps.stats[STAT_HITSTUN_TIME] <= 0 + && !( client->ps.pm_flags & PMF_BLOCK ) + && ( ( ucmd->buttons & BUTTON_KI_USE ) // BFP - Using Ki + || ( client->ps.eFlags & EF_KI_BOOST ) ) // BFP - When "kiusetoggle" is binded, enables/disables + && client->ps.weaponstate != WEAPON_STUN ) { + client->ps.eFlags |= EF_KI_BOOST; // Handle ki boost status + client->ps.eFlags |= EF_AURA; + } else { + if ( !( client->ps.pm_flags & PMF_BLOCK ) // BFP - Handle block status from this conditional + && !( ucmd->buttons & BUTTON_KI_CHARGE ) ) { // BFP - If it's charging while it was using ki boost, don't remove the aura! + client->ps.eFlags &= ~EF_AURA; + client->ps.eFlags &= ~EF_KI_BOOST; // Handle ki boost status + } + } + + // BFP - Block, reflect ki attacks and reduce health damage + BlockHandling( client, ucmd ); + + // BFP - Melee handling + MeleeHandling( ent, ucmd, &pm ); + + // BFP - Ki Charge + if ( ( ucmd->buttons & BUTTON_KI_CHARGE ) && ( client->ps.pm_flags & PMF_KI_CHARGE ) + && client->ps.pm_time > 0 ) { // still delayed + client->ps.eFlags &= ~EF_AURA; + } + if ( ( client->ps.pm_flags & PMF_KI_CHARGE ) + && client->ps.pm_time <= 0 ) { // charge ki! + client->ps.eFlags |= EF_AURA; + } } + // BFP - No flight + pm.noFlight = qfalse; + if ( g_noFlight.integer > 0 ) { + pm.noFlight = qtrue; + } + + // BFP - Melee only + pm.meleeOnly = qfalse; + if ( g_meleeOnly.integer > 0 ) { + pm.meleeOnly = qtrue; + } + + // BFP - No force gesture anim +#if 0 if ( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; - ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + client->pers.cmd.buttons |= BUTTON_GESTURE; } +#endif pm.ps = &client->ps; pm.cmd = *ucmd; @@ -800,23 +1173,21 @@ void ClientThink_real( gentity_t *ent ) { Pmove (&pm); // save results of pmove - if ( ent->client->ps.eventSequence != oldEventSequence ) { + if ( client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } if (g_smoothClients.integer) { - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + BG_PlayerStateToEntityStateExtraPolate( &client->ps, &ent->s, client->ps.commandTime, qtrue ); } else { - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); } - SendPendingPredictableEvents( &ent->client->ps ); + SendPendingPredictableEvents( &client->ps ); -// BFP - no hook -#if 0 - if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { + // BFP - Beam fire hold handling + if ( !( client->ps.eFlags & EF_FIRING ) ) { client->fireHeld = qfalse; // for grapple } -#endif // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); @@ -832,12 +1203,12 @@ void ClientThink_real( gentity_t *ent ) { // link entity now, after any personal teleporters have been used trap_LinkEntity (ent); - if ( !ent->client->noclip ) { + if ( !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 ); + VectorCopy( client->ps.origin, ent->r.currentOrigin ); //test for solid areas in the AAS file BotTestAAS(ent->r.currentOrigin); @@ -846,7 +1217,7 @@ void ClientThink_real( gentity_t *ent ) { ClientImpacts( ent, &pm ); // save results of triggers and client events - if (ent->client->ps.eventSequence != oldEventSequence) { + if (client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } @@ -917,13 +1288,13 @@ SpectatorClientEndFrame ================== */ void SpectatorClientEndFrame( gentity_t *ent ) { - gclient_t *cl; + gclient_t *cl, *client; - // if we are doing a chase cam or a remote view, grab the latest info - if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { - int clientNum, flags; + client = ent->client; - clientNum = ent->client->sess.spectatorClient; + // if we are doing a chase cam or a remote view, grab the latest info + if ( client->sess.spectatorState == SPECTATOR_FOLLOW ) { + int clientNum = client->sess.spectatorClient; // team follow1 and team follow2 go to whatever clients are playing if ( clientNum == -1 ) { @@ -931,32 +1302,32 @@ void SpectatorClientEndFrame( gentity_t *ent ) { } else if ( clientNum == -2 ) { clientNum = level.follow2; } - if ( clientNum >= 0 ) { + if ( (unsigned)clientNum < MAX_CLIENTS ) { cl = &level.clients[ clientNum ]; if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { - flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); - ent->client->ps = cl->ps; - ent->client->ps.pm_flags |= PMF_FOLLOW; - ent->client->ps.eFlags = flags; + int flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); + client->ps = cl->ps; + client->ps.pm_flags |= PMF_FOLLOW; + 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 ( client->sess.spectatorClient >= 0 ) { + client->sess.spectatorState = SPECTATOR_FREE; + ClientBegin( client - level.clients ); } } } } // BFP - PMF_SCOREBOARD is unused - /* - if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { - ent->client->ps.pm_flags |= PMF_SCOREBOARD; +#if 0 + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + client->ps.pm_flags |= PMF_SCOREBOARD; } else { - ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; + client->ps.pm_flags &= ~PMF_SCOREBOARD; } - */ +#endif } /* @@ -970,15 +1341,12 @@ while a slow client may have multiple ClientEndFrame between ClientThink. */ void ClientEndFrame( gentity_t *ent ) { int i; - clientPersistant_t *pers; if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { SpectatorClientEndFrame( ent ); return; } - pers = &ent->client->pers; - // turn off any expired powerups for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( ent->client->ps.powerups[ i ] < level.time ) { diff --git a/source/game/g_arenas.c b/source/game/g_arenas.c index b2951bd..f0f4d8f 100644 --- a/source/game/g_arenas.c +++ b/source/game/g_arenas.c @@ -42,7 +42,6 @@ void UpdateTournamentInfo( void ) { gentity_t *player; int playerClientNum; int n, accuracy, perfect, msglen; - int buflen; char buf[32]; char msg[MAX_STRING_CHARS]; @@ -77,18 +76,25 @@ void UpdateTournamentInfo( void ) { } perfect = ( level.clients[playerClientNum].ps.persistant[PERS_RANK] == 0 && player->client->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; + // BFP - No impressive, gauntlet, defend and assist counters +#if 0 Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], perfect ); +#endif + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + 0, player->client->ps.persistant[PERS_EXCELLENT_COUNT], + 0, player->client->ps.persistant[PERS_SCORE], + perfect ); } msglen = (int)strlen( msg ); for( i = 0; i < level.numNonSpectatorClients; i++ ) { n = level.sortedClients[i]; Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); - buflen = (int)strlen( buf ); - if( msglen + buflen + 1 >= sizeof(msg) ) { + msglen += (int)strlen( buf ); + if( msglen >= sizeof(msg)-1 ) { break; } strcat( msg, buf ); diff --git a/source/game/g_bot.c b/source/game/g_bot.c index 32a48e4..4c32721 100644 --- a/source/game/g_bot.c +++ b/source/game/g_bot.c @@ -43,7 +43,6 @@ typedef struct { int spawnTime; } botSpawnQueue_t; -//static int botBeginDelay = 0; // bk001206 - unused, init static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; vmCvar_t bot_minplayers; @@ -52,6 +51,8 @@ extern gentity_t *podium1; extern gentity_t *podium2; extern gentity_t *podium3; +extern char mapname[ MAX_QPATH ]; + float trap_Cvar_VariableValue( const char *var_name ) { char buf[128]; @@ -108,7 +109,7 @@ int G_ParseInfos( char *buf, int max, char *infos[] ) { Info_SetValueForKey( info, key, token ); } //NOTE: extra space for arena number - infos[count] = G_Alloc((int)strlen(info) + (int)strlen("\\num\\") + (int)strlen(va("%d", MAX_ARENAS)) + 1); + infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); if (infos[count]) { strcpy(infos[count], info); count++; @@ -117,18 +118,19 @@ int G_ParseInfos( char *buf, int max, char *infos[] ) { return count; } + /* =============== G_LoadArenasFromFile =============== */ -static void G_LoadArenasFromFile( char *filename ) { +static void G_LoadArenasFromFile( const char *filename ) { int len; fileHandle_t f; char buf[MAX_ARENAS_TEXT]; len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { + if ( f == FS_INVALID_HANDLE ) { trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); return; } @@ -145,6 +147,7 @@ static void G_LoadArenasFromFile( char *filename ) { g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); } + /* =============== G_LoadArenas @@ -170,7 +173,7 @@ static void G_LoadArenas( void ) { } // get all arenas from .arena files - numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); + numdirs = trap_FS_GetFileList( "scripts", ".arena", dirlist, sizeof( dirlist ) ); dirptr = dirlist; for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { dirlen = (int)strlen(dirptr); @@ -229,27 +232,28 @@ static void PlayerIntroSound( const char *modelAndSkin ) { trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); } + /* =============== G_AddRandomBot =============== */ -void G_AddRandomBot( int team ) { +void G_AddRandomBot( team_t team ) { int i, n, num; float skill; - char *value, netname[36], *teamstr; + char *value, netname[36], *teamstr, *skillstr; 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++ ) { + for ( i = 0 ; i < level.maxclients ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { @@ -259,20 +263,27 @@ void G_AddRandomBot( int team ) { break; } } - if (i >= g_maxclients.integer) { + if (i >= level.maxclients) { 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++ ) { + + value = Info_ValueForKey( g_botInfos[ n ], "name" ); + + skillstr = Info_ValueForKey( g_botInfos[ n ], "skill" ); + if ( *skillstr ) + skill = atof( skillstr ); + else + skill = trap_Cvar_VariableValue( "g_spSkill" ); + + for ( i = 0 ; i < level.maxclients ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { @@ -282,23 +293,22 @@ void G_AddRandomBot( int team ) { break; } } - if (i >= g_maxclients.integer) { + if (i >= level.maxclients) { num--; - if (num <= 0) { - skill = trap_Cvar_VariableValue( "g_spSkill" ); + if ( num <= 0 ) { 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_strncpyz(netname, value, sizeof(netname)); Q_CleanStr(netname); - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); + trap_SendConsoleCommand( EXEC_INSERT, va( "addbot %s %1.2f %s 0\n", netname, skill, teamstr ) ); return; } } } } + /* =============== G_RemoveRandomBot @@ -309,12 +319,12 @@ int G_RemoveRandomBot( int team ) { char netname[36]; gclient_t *cl; - for ( i=0 ; i< g_maxclients.integer ; i++ ) { + for ( i = 0 ; i < level.maxclients ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { @@ -328,22 +338,23 @@ int G_RemoveRandomBot( int team ) { return qfalse; } + /* =============== G_CountHumanPlayers =============== */ -int G_CountHumanPlayers( int team ) { +static int G_CountHumanPlayers( team_t team ) { int i, num; gclient_t *cl; num = 0; - for ( i=0 ; i< g_maxclients.integer ; i++ ) { + for ( i = 0 ; i < level.maxclients ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } - if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { + if ( g_entities[i].r.svFlags & SVF_BOT ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { @@ -354,22 +365,23 @@ int G_CountHumanPlayers( int team ) { return num; } + /* =============== G_CountBotPlayers =============== */ -int G_CountBotPlayers( int team ) { +static int G_CountBotPlayers( team_t team ) { int i, n, num; gclient_t *cl; num = 0; - for ( i=0 ; i< g_maxclients.integer ; i++ ) { + for ( i=0 ; i< level.maxclients ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { @@ -389,6 +401,7 @@ int G_CountBotPlayers( int team ) { return num; } + /* =============== G_CheckMinimumPlayers @@ -399,19 +412,24 @@ void G_CheckMinimumPlayers( void ) { int humanplayers, botplayers; static int checkminimumplayers_time; - if (level.intermissiontime) return; + if ( level.intermissiontime ) + return; + //only check once each 10 seconds - if (checkminimumplayers_time > level.time - 10000) { + if ( checkminimumplayers_time > level.time - 10000 ) return; - } + + if ( level.time - level.startTime < 2000 ) + 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; + if (minplayers >= level.maxclients / 2) { + minplayers = (level.maxclients / 2) -1; } humanplayers = G_CountHumanPlayers( TEAM_RED ); @@ -432,9 +450,10 @@ void G_CheckMinimumPlayers( void ) { G_RemoveRandomBot( TEAM_BLUE ); } } - else if (g_gametype.integer == GT_TOURNAMENT ) { - if (minplayers >= g_maxclients.integer) { - minplayers = g_maxclients.integer-1; + else if ( g_gametype.integer == GT_TOURNAMENT + || g_gametype.integer == GT_SURVIVAL ) { // BFP - Survival + if ( minplayers >= level.maxclients ) { + minplayers = level.maxclients - 1; } humanplayers = G_CountHumanPlayers( -1 ); botplayers = G_CountBotPlayers( -1 ); @@ -450,8 +469,8 @@ void G_CheckMinimumPlayers( void ) { } } else if (g_gametype.integer == GT_FFA) { - if (minplayers >= g_maxclients.integer) { - minplayers = g_maxclients.integer-1; + if (minplayers >= level.maxclients) { + minplayers = level.maxclients-1; } humanplayers = G_CountHumanPlayers( TEAM_FREE ); botplayers = G_CountBotPlayers( TEAM_FREE ); @@ -464,6 +483,7 @@ void G_CheckMinimumPlayers( void ) { } } + /* =============== G_CheckBotSpawn @@ -509,7 +529,8 @@ static void AddBotToSpawnQueue( int clientNum, int delay ) { } } - G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); + G_Printf( S_COLOR_YELLOW "Unable to delay bot spawn\n" ); + ClientBegin( clientNum ); } @@ -563,16 +584,17 @@ qboolean G_BotConnect( int clientNum, qboolean restart ) { G_AddBot =============== */ -static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { +static void G_AddBot( const char *name, float skill, const char *team, int delay, const char *altname ) { int clientNum; char *botinfo; gentity_t *bot; char *key; char *s; - char *botname; - char *model; - char *headmodel; + const char *botname; + const char *model; + const char *headmodel; char userinfo[MAX_INFO_STRING]; + char nm[MAX_CVAR_VALUE_STRING]; // get the botinfo from bots.txt botinfo = G_GetBotInfoByName( name ); @@ -592,11 +614,16 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay if (altname && altname[0]) { botname = altname; } - Info_SetValueForKey( userinfo, "name", botname ); + + BG_CleanName( botname, nm, sizeof( nm ), "unnamed bot" ); + Info_SetValueForKey( userinfo, "name", nm ); + Info_SetValueForKey( userinfo, "rate", "25000" ); Info_SetValueForKey( userinfo, "snaps", "20" ); Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + // BFP - No handicap +#if 0 if ( skill >= 1 && skill < 2 ) { Info_SetValueForKey( userinfo, "handicap", "50" ); } @@ -606,6 +633,7 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay else if ( skill >= 3 && skill < 4 ) { Info_SetValueForKey( userinfo, "handicap", "90" ); } +#endif key = "model"; model = Info_ValueForKey( botinfo, key ); @@ -613,8 +641,10 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay model = "visor/default"; } Info_SetValueForKey( userinfo, key, model ); - key = "team_model"; - Info_SetValueForKey( userinfo, key, model ); + if ( g_gametype.integer >= GT_TEAM ) { + key = "team_model"; + Info_SetValueForKey( userinfo, key, model ); + } key = "headmodel"; headmodel = Info_ValueForKey( botinfo, key ); @@ -622,8 +652,10 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay headmodel = model; } Info_SetValueForKey( userinfo, key, headmodel ); - key = "team_headmodel"; - Info_SetValueForKey( userinfo, key, headmodel ); + if ( g_gametype.integer >= GT_TEAM ) { + key = "team_headmodel"; + Info_SetValueForKey( userinfo, key, headmodel ); + } key = "gender"; s = Info_ValueForKey( botinfo, key ); @@ -632,6 +664,8 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay } Info_SetValueForKey( userinfo, "sex", s ); + // BFP - No color1 and color2 +#if 0 key = "color1"; s = Info_ValueForKey( botinfo, key ); if ( !*s ) { @@ -645,6 +679,7 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay s = "5"; } Info_SetValueForKey( userinfo, key, s ); +#endif s = Info_ValueForKey(botinfo, "aifile"); if (!*s ) { @@ -660,22 +695,14 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay return; } - // initialize the bot settings - if( !team || !*team ) { - if( g_gametype.integer >= GT_TEAM ) { - if( PickTeam(clientNum) == TEAM_RED) { - team = "red"; - } - else { - team = "blue"; - } - } - else { - team = "red"; - } + // cleanup previous data manually + // because client may silently (re)connect without ClientDisconnect in case of crash for example + if ( level.clients[ clientNum ].pers.connected != CON_DISCONNECTED ) { + ClientDisconnect( clientNum ); } + Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); - Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); + Info_SetValueForKey( userinfo, "skill", va( "%1.2f", skill ) ); Info_SetValueForKey( userinfo, "team", team ); bot = &g_entities[ clientNum ]; @@ -690,7 +717,7 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay return; } - if( delay == 0 ) { + if ( delay == 0 ) { ClientBegin( clientNum ); return; } @@ -731,6 +758,10 @@ void Svcmd_AddBot_f( void ) { } else { skill = atof( string ); + if ( skill < 1 ) + skill = 1; + else if ( skill > 5 ) + skill = 5; } // team @@ -754,7 +785,7 @@ void Svcmd_AddBot_f( void ) { // go ahead and load the bot's media immediately if ( level.time - level.startTime > 1000 && trap_Cvar_VariableIntegerValue( "cl_running" ) ) { - trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo + trap_SendServerCommand( -1, "loaddefered\n" ); } } @@ -765,30 +796,30 @@ Svcmd_BotList_f */ void Svcmd_BotList_f( void ) { int i; - char name[MAX_TOKEN_CHARS]; - char funname[MAX_TOKEN_CHARS]; - char model[MAX_TOKEN_CHARS]; - char aifile[MAX_TOKEN_CHARS]; - - trap_Printf("^1name model aifile funname\n"); - for (i = 0; i < g_numBots; i++) { - strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); + char name[MAX_NETNAME]; + char funname[MAX_NETNAME]; + char model[MAX_QPATH]; + char aifile[MAX_QPATH]; + + trap_Printf( S_COLOR_RED "name model aifile funname\n" ); + for ( i = 0; i < g_numBots; i++ ) { + Q_strncpyz( name, Info_ValueForKey( g_botInfos[i], "name" ), sizeof( name ) ); if ( !*name ) { strcpy(name, "UnnamedPlayer"); } - strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); + Q_strncpyz( funname, Info_ValueForKey( g_botInfos[i], "funname" ), sizeof( funname ) ); if ( !*funname ) { - strcpy(funname, ""); + strcpy( funname, "" ); } - strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); + Q_strncpyz( model, Info_ValueForKey( g_botInfos[i], "model" ), sizeof( model ) ); if ( !*model ) { - strcpy(model, "visor/default"); + strcpy( model, "visor/default" ); } - strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile")); - if (!*aifile ) { - strcpy(aifile, "bots/default_c.c"); + Q_strncpyz( aifile, Info_ValueForKey( g_botInfos[i], "aifile" ), sizeof( aifile ) ); + if ( !*aifile ) { + strcpy( aifile, "bots/default_c.c" ); } - trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); + trap_Printf( va( "%-16s %-16s %-20s %-20s\n", name, model, aifile, funname ) ); } } @@ -798,7 +829,7 @@ void Svcmd_BotList_f( void ) { G_SpawnBots =============== */ -static void G_SpawnBots( char *botList, int baseDelay ) { +static void G_SpawnBots( const char *botList, int baseDelay ) { char *bot; char *p; float skill; @@ -819,15 +850,15 @@ static void G_SpawnBots( char *botList, int baseDelay ) { skill = 5; } - Q_strncpyz( bots, botList, sizeof(bots) ); + Q_strncpyz( bots, botList, sizeof( bots ) ); p = &bots[0]; delay = baseDelay; while( *p ) { //skip spaces - while( *p && *p == ' ' ) { + while( *p == ' ' ) { p++; } - if( !p ) { + if( !*p ) { break; } @@ -839,7 +870,7 @@ static void G_SpawnBots( char *botList, int baseDelay ) { p++; } if( *p ) { - *p++ = 0; + *p++ = '\0'; } // we must add the bot this way, calling G_AddBot directly at this stage @@ -856,29 +887,30 @@ static void G_SpawnBots( char *botList, int baseDelay ) { G_LoadBotsFromFile =============== */ -static void G_LoadBotsFromFile( char *filename ) { +static void G_LoadBotsFromFile( const char *filename ) { int len; fileHandle_t f; char buf[MAX_BOTS_TEXT]; len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { + if ( f == FS_INVALID_HANDLE ) { 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_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i\n", filename, len, MAX_BOTS_TEXT ) ); trap_FS_FCloseFile( f ); return; } trap_FS_Read( buf, len, f ); - buf[len] = 0; trap_FS_FCloseFile( f ); + buf[ len ] = '\0'; g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); } + /* =============== G_LoadBots @@ -899,16 +931,16 @@ static void G_LoadBots( void ) { g_numBots = 0; - trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); - if( *botsFile.string ) { - G_LoadBotsFromFile(botsFile.string); - } - else { - G_LoadBotsFromFile("scripts/bots.txt"); + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_ARCHIVE | CVAR_LATCH ); + + if ( *botsFile.string && g_gametype.integer != GT_SINGLE_PLAYER ) { + G_LoadBotsFromFile( botsFile.string ); + } else { + G_LoadBotsFromFile( "scripts/bots.txt" ); } // get all bots from .bot files - numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); + numdirs = trap_FS_GetFileList( "scripts", ".bot", dirlist, sizeof( dirlist ) ); dirptr = dirlist; for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { dirlen = (int)strlen(dirptr); @@ -954,6 +986,7 @@ char *G_GetBotInfoByName( const char *name ) { return NULL; } + /* =============== G_InitBots @@ -965,8 +998,6 @@ void G_InitBots( qboolean restart ) { const char *arenainfo; char *strValue; int basedelay; - char map[MAX_QPATH]; - char serverinfo[MAX_INFO_STRING]; G_LoadBots(); G_LoadArenas(); @@ -974,9 +1005,7 @@ void G_InitBots( qboolean restart ) { 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 ); + arenainfo = G_GetArenaInfoByMap( mapname ); if ( !arenainfo ) { return; } diff --git a/source/game/g_client.c b/source/game/g_client.c index cfd017b..5a072d9 100644 --- a/source/game/g_client.c +++ b/source/game/g_client.c @@ -28,6 +28,8 @@ static vec3_t playerMins = {-15, -15, -24}; static vec3_t playerMaxs = {15, 15, 32}; #define MAX_SPAWN_POINTS 128 +static char ban_reason[MAX_CVAR_VALUE_STRING]; + /*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. @@ -81,18 +83,18 @@ SpotWouldTelefrag */ qboolean SpotWouldTelefrag( gentity_t *spot ) { int i, num; - int touch[MAX_GENTITIES]; + int touch[MAX_CLIENTS]; 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 ); + num = G_EntitiesInBox( mins, maxs, touch, level.maxclients ); - for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { - if ( hit->client) { + if ( hit->client ) { return qtrue; } @@ -264,7 +266,7 @@ static gentity_t *SelectRandomFurthestSpawnPoint( const gentity_t *ent, vec3_t a VectorCopy( spot->s.angles, angles ); VectorCopy( spot->s.origin, origin ); - origin[2] += 9.0f; + origin[2] += SPAWN_HEIGHT; return spot; } @@ -310,7 +312,7 @@ gentity_t *SelectInitialSpawnPoint( gentity_t *ent, vec3_t origin, vec3_t angles VectorCopy( spot->s.angles, angles ); VectorCopy( spot->s.origin, origin ); - origin[2] += 9.0f; + origin[2] += SPAWN_HEIGHT; return spot; } @@ -388,6 +390,11 @@ void CopyToBodyQue( gentity_t *ent ) { trap_UnlinkEntity (ent); + // don't leave a corpse if already gibbed + if ( ent->s.eType == ET_INVISIBLE && ent->health <= GIB_HEALTH ) { + return; + } + // if client is in a nodrop area, don't leave the body contents = trap_PointContents( ent->s.origin, -1 ); if ( contents & CONTENTS_NODROP ) { @@ -450,12 +457,16 @@ void CopyToBodyQue( gentity_t *ent ) { body->die = body_die; + // BFP - Attention: DLL & SO are prone to crash here, so setting body->takedamage as qfalse avoids that + body->takedamage = qfalse; +#if 0 // don't take more damage if already gibbed if ( ent->health <= GIB_HEALTH ) { body->takedamage = qfalse; } else { body->takedamage = qtrue; } +#endif VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); @@ -596,89 +607,6 @@ static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) } */ -/* -=========== -ClientCheckName -============ -*/ -static void ClientCleanName( const char *in, char *out, int outSize ) { - int len, colorlessLen; - char ch; - char *p; - int spaces; - - //save room for trailing null byte - outSize--; - - len = 0; - colorlessLen = 0; - p = out; - *p = 0; - spaces = 0; - - while( 1 ) { - ch = *in++; - if( !ch ) { - break; - } - - // don't allow leading spaces - if( !*p && ch == ' ' ) { - continue; - } - - // check colors - if( ch == Q_COLOR_ESCAPE ) { - // solo trailing carat is not a color prefix - if( !*in ) { - break; - } - - // don't allow black in a name, period - if( ColorIndex(*in) == 0 ) { - in++; - continue; - } - - // make sure room in dest for both chars - if( len > outSize - 2 ) { - break; - } - - *out++ = ch; - *out++ = *in++; - len += 2; - continue; - } - - // don't allow too many consecutive spaces - if( ch == ' ' ) { - spaces++; - if( spaces > 3 ) { - continue; - } - } - else { - spaces = 0; - } - - if( len > outSize - 1 ) { - break; - } - - *out++ = ch; - colorlessLen++; - len++; - } - *out = 0; - - // don't allow empty names - if( *p == 0 || colorlessLen == 0 ) { - Q_strncpyz( p, "UnnamedPlayer", outSize ); - } -} - - /* =========== ClientUserInfoChanged @@ -690,28 +618,42 @@ The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ -void ClientUserinfoChanged( int clientNum ) { +qboolean ClientUserinfoChanged( int clientNum ) { gentity_t *ent; - int teamTask, teamLeader, team, health; + team_t team; + // BFP - No handicap for health + // int health; char *s; char model[MAX_QPATH]; - char headModel[MAX_QPATH]; + // BFP - Check model in list + char modelCheck[MAX_QPATH]; char oldname[MAX_STRING_CHARS]; gclient_t *client; - char c1[MAX_INFO_STRING]; - char c2[MAX_INFO_STRING]; - char redTeam[MAX_INFO_STRING]; - char blueTeam[MAX_INFO_STRING]; + // BFP - No color1 + // char c1[MAX_INFO_STRING]; char userinfo[MAX_INFO_STRING]; + // BFP - Model prefix load + char newModelPrefix[MAX_QPATH]; + char *oldModelDash, *newModelDash; + ent = g_entities + clientNum; client = ent->client; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // check for malformed or illegal info strings - if ( !Info_Validate(userinfo) ) { - strcpy (userinfo, "\\name\\badinfo"); + if ( !Info_Validate( userinfo ) ) { + Q_strcpy( ban_reason, "bad userinfo" ); + if ( client && client->pers.connected != CON_DISCONNECTED ) + trap_DropClient( clientNum, ban_reason ); + return qfalse; + } + + if ( client->pers.connected == CON_DISCONNECTED ) { + // we just checked if connecting player can join server + // so quit now as some important data like player team is still not set + return qtrue; } // check for local client @@ -731,7 +673,7 @@ void ClientUserinfoChanged( int clientNum ) { // set name Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey (userinfo, "name"); - ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); + BG_CleanName( s, client->pers.netname, sizeof( client->pers.netname ), "UnnamedPlayer" ); if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { @@ -746,6 +688,8 @@ void ClientUserinfoChanged( int clientNum ) { } } + // BFP - No handicap +#if 0 // set max health health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); client->pers.maxHealth = health; @@ -753,14 +697,47 @@ void ClientUserinfoChanged( int clientNum ) { client->pers.maxHealth = 100; } client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; +#endif + + client->ps.stats[STAT_MAX_HEALTH] = 1 + client->ps.persistant[PERS_POWERLEVEL]; + if ( client->ps.stats[STAT_MAX_HEALTH] > 1000 ) { + client->ps.stats[STAT_MAX_HEALTH] = 1000; + } + + // BFP - Monster gamemode, double max health for player monster + if ( g_gametype.integer == GT_MONSTER + && level.monsterClientNum == clientNum + && ( client->ps.eFlags & EF_MONSTER ) ) { + client->ps.stats[STAT_MAX_HEALTH] *= 2; + if ( client->ps.stats[STAT_MAX_HEALTH] > 2000 ) { + client->ps.stats[STAT_MAX_HEALTH] = 2000; + } + } // set model - if( g_gametype.integer >= GT_TEAM ) { - Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); - Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); - } else { - Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); - Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); + // BFP - Resolve player model when loading by prefix + G_ResolvePlayerModel( userinfo, model, model, sizeof( model ) ); + + // BFP - Kick/force to spectate the player who uses an illegal model which isn't available in the server + Q_strncpyz( modelCheck, G_GetPlayerModelName( clientNum, userinfo ), sizeof( modelCheck ) ); + if ( !G_PlayerModelExistsOnServer( modelCheck ) + && ( g_gametype.integer != GT_MONSTER + || ( g_gametype.integer == GT_MONSTER && g_monster.integer < 1 ) ) ) { +#if KICK_ILLEGAL_PLAYER_MODEL + if ( client && client->pers.connected != CON_DISCONNECTED ) { + trap_DropClient( clientNum, "was kicked" ); + } +#else + gentity_t *tempEnt = G_TempEntity( ent->r.currentOrigin, EV_OBITUARY ); + tempEnt->s.eventParm = MOD_ILLEGAL_PLAYER_MODEL; + tempEnt->r.svFlags = SVF_BROADCAST; + client->sess.sessionTeam = TEAM_SPECTATOR; + client->sess.spectatorState = SPECTATOR_FREE; + client->sess.spectatorClient = 0; + client->sess.teamLeader = qfalse; + ClientBegin( clientNum ); +#endif + return qfalse; } // bots set their team a few frames later @@ -779,27 +756,6 @@ void ClientUserinfoChanged( int clientNum ) { team = client->sess.sessionTeam; } -/* NOTE: all client side now - - // team - switch( team ) { - case TEAM_RED: - ForceClientSkin(client, model, "red"); -// ForceClientSkin(client, headModel, "red"); - break; - case TEAM_BLUE: - ForceClientSkin(client, model, "blue"); -// ForceClientSkin(client, headModel, "blue"); - break; - } - // don't ever use a default skin in teamplay, it would just waste memory - // however bots will always join a team but they spawn in as spectator - if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { - ForceClientSkin(client, model, "red"); -// ForceClientSkin(client, headModel, "red"); - } -*/ - // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); if ( ! *s || atoi( s ) != 0 ) { @@ -807,47 +763,160 @@ void ClientUserinfoChanged( int clientNum ) { } else { client->pers.teamInfo = qfalse; } - /* - s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); - if ( !*s || atoi( s ) == 0 ) { - client->pers.pmoveFixed = qfalse; - } - else { - client->pers.pmoveFixed = qtrue; - } - */ - - // team task (0 = none, 1 = offence, 2 = defence) - teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); - // team Leader (1 = leader, 0 is normal player) - teamLeader = client->sess.teamLeader; + // BFP - No color1 +#if 0 // colors strcpy(c1, Info_ValueForKey( userinfo, "color1" )); - strcpy(c2, Info_ValueForKey( userinfo, "color2" )); - - strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); - strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); +#endif // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds if ( ent->r.svFlags & SVF_BOT ) { - s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", - client->pers.netname, team, model, headModel, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, - Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); + s = va("n\\%s\\t\\%i\\model\\%s\\w\\%i\\l\\%i\\skill\\%s", + client->pers.netname, team, model, + client->sess.wins, client->sess.losses, + Info_ValueForKey( userinfo, "skill" ) ); } else { - s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", - client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); + s = va("n\\%s\\t\\%i\\model\\%s\\w\\%i\\l\\%i", + client->pers.netname, client->sess.sessionTeam, model, + client->sess.wins, client->sess.losses ); } trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + // BFP - Model prefix handling + { + // extract model prefixes safely + oldModelDash = strchr(ent->oldModel, '-'); + if ( oldModelDash ) { + Q_strncpyz( ent->oldModelPrefix, ent->oldModel, oldModelDash - ent->oldModel + 1 ); + } else { + Q_strncpyz( ent->oldModelPrefix, ent->oldModel, sizeof( ent->oldModelPrefix ) ); + } + + newModelDash = strchr(model, '-'); + if ( newModelDash ) { + Q_strncpyz( newModelPrefix, model, newModelDash - model + 1 ); + } else { + Q_strncpyz( newModelPrefix, model, sizeof( newModelPrefix ) ); + } + + // compare model prefixes + if ( Q_stricmp( ent->oldModelPrefix, newModelPrefix ) + && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { // only when the player is playing + // prefixes differ, kill the player + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die( ent, ent, NULL, 100000, MOD_UNKNOWN ); + } + + // save the new model as the old model for the next time this function runs + Q_strncpyz( ent->oldModel, model, sizeof( ent->oldModel ) ); + } + + // BFP - Send powerlevel info to cgame reusing frame from entityState_t struct + ent->s.frame = client->ps.persistant[PERS_POWERLEVEL]; + // this is not the userinfo, more like the configstring actually G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); + + return qtrue; } +/* +=========== +ClientGetAveragePowerlevel + +Get the average powerlevel when there are another players with +different powerlevels. +============ +*/ +static int ClientGetAveragePowerlevel( void ) { // BFP - Average powerlevel + int i = 0; + int totalPowerLevel = 0; + int activeClients = 0; + + while ( i < level.numConnectedClients ) { + gentity_t *ent = &g_entities[level.sortedClients[i]]; + + if ( ent->inuse && ent->client ) { + totalPowerLevel += ent->client->ps.persistant[PERS_POWERLEVEL]; + ++activeClients; + } + + // BFP - Survival + if ( g_gametype.integer == GT_SURVIVAL + && ent->inuse && ent->client && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + // get the same powerlevel of the standing one for the sake of balance + // avoid division by zero + return ( activeClients > 0 ) ? ent->client->ps.persistant[PERS_POWERLEVEL] : 0; + } + ++i; + } + + // avoid division by zero + return ( activeClients > 0 ) ? ( totalPowerLevel / activeClients ) : 0; +} + +/* +================== +ClientBecomeMonster + +Sets the player monster status and properties. +================== +*/ +static void ClientBecomeMonster( gentity_t *ent ) { // BFP - Monster gamemode function to set the player monster status + // double health and max health + ent->client->ps.stats[STAT_MAX_HEALTH] *= 2; + if ( ent->client->ps.stats[STAT_MAX_HEALTH] > 2000 ) { + ent->client->ps.stats[STAT_MAX_HEALTH] = 2000; + } + ent->health = ent->client->ps.stats[STAT_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH]; + + // double ki and max ki + ent->client->ps.stats[STAT_MAX_KI] *= 2; + ent->client->ps.stats[STAT_KI] = ent->client->ps.stats[STAT_MAX_KI]; + + ent->client->ps.eFlags |= EF_MONSTER; + + trap_SendServerCommand( -1, va("print \"%s is the monster\n\"", ent->client->pers.netname) ); +} + +/* +=========== +ClientCheckMonsterGone + +Check if the player monster is gone and set the monster +to the most waited player. +============ +*/ +void ClientCheckMonsterGone( gentity_t *ent ) { // BFP - Monster gamemode function check + if ( g_gametype.integer == GT_MONSTER + && ( ent->client->ps.eFlags & EF_MONSTER ) + && ent->client->ps.clientNum == level.monsterClientNum ) { + qboolean becameMonster = qfalse; + int i; + + ent->client->ps.eFlags &= ~EF_MONSTER; + + for ( i = 0 ; i < level.maxclients ; ++i ) { + if ( g_entities[i].client->pers.connected == CON_CONNECTED + && g_entities[i].client->sess.sessionTeam != TEAM_SPECTATOR + && ent->client->ps.clientNum != g_entities[i].client->ps.clientNum ) { + level.monsterClientNum = g_entities[i].client->ps.clientNum; + g_entities[i].client->ps.eFlags |= EF_MONSTER; + becameMonster = qtrue; + respawn( &g_entities[i] ); + break; + } + } + // a check to detect if no one is here, so reset the value, otherwise whoever joins won't become a monster + if ( !becameMonster ) { + level.monsterClientNum = -1; + } + } +} /* =========== @@ -869,14 +938,40 @@ to the server machine, but qfalse on map changes and tournement restarts. ============ */ -char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { +const char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { char *value; // char *areabits; gclient_t *client; char userinfo[MAX_INFO_STRING]; gentity_t *ent; + qboolean isAdmin = qfalse; + + if ( clientNum >= level.maxclients ) { + return "Bad connection slot."; + } ent = &g_entities[ clientNum ]; + ent->client = level.clients + clientNum; + + if ( firstTime ) { + // cleanup previous data manually + // because client may silently (re)connect without ClientDisconnect in case of crash for example + if ( level.clients[ clientNum ].pers.connected != CON_DISCONNECTED ) + ClientDisconnect( clientNum ); + + // remove old entity from the world + trap_UnlinkEntity( ent ); + ent->r.contents = 0; + ent->s.eType = ET_INVISIBLE; + ent->s.eFlags = 0; + ent->s.modelindex = 0; + ent->s.clientNum = clientNum; + ent->s.number = clientNum; + ent->takedamage = qfalse; + } + + ent->r.svFlags &= ~SVF_BOT; + ent->inuse = qfalse; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); @@ -884,20 +979,24 @@ char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 // recommanding PB based IP / GUID banning, the builtin system is pretty limited // check to see if they are on the banned IP list - value = Info_ValueForKey (userinfo, "ip"); - if ( G_FilterPacket( value ) ) { + value = Info_ValueForKey( userinfo, "ip" ); + + if ( !strcmp( value, "localhost" ) && !isBot ) + isAdmin = qtrue; + + if ( !isAdmin && G_FilterPacket( value ) ) { return "You are banned from this server."; } - // we don't check password for bots and local client - // NOTE: local client <-> "ip" "localhost" - // this means this client is not running in our current process - if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { + // 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 ( !isBot && !isAdmin ) { // 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"; + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) ) { + value = Info_ValueForKey( userinfo, "password" ); + if ( strcmp( g_password.string, value ) ) + return "Invalid password"; } } @@ -905,28 +1004,74 @@ char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { ent->client = level.clients + clientNum; client = ent->client; + // BFP - Monster gamemode + if ( level.monsterClientNum == -1 ) { + // first player becomes the monster + level.monsterClientNum = clientNum; + } + // areabits = client->areabits; memset( client, 0, sizeof(*client) ); + + // BFP - Monster gamemode + if ( clientNum == level.monsterClientNum ) { + client->ps.eFlags |= EF_MONSTER; + } - client->pers.connected = CON_CONNECTING; + if ( !ClientUserinfoChanged( clientNum ) ) { + return ban_reason; + } + + // BFP - Survival + if ( g_gametype.integer == GT_SURVIVAL ) { + // make sure the player isn't playing and must be spectator + // when the match is restarted or the map is changed + if ( !firstTime && client->sess.sessionTeam != TEAM_SPECTATOR ) { + client->sess.sessionTeam = TEAM_SPECTATOR; + // output "joined to ..." message + BroadcastTeamChange( client, -1 ); + } + + // output "joined to ..." message + if ( level.newSession ) { + BroadcastTeamChange( client, -1 ); + } + + // make sure the player score isn't negative when being connected at that moment + if ( client->ps.persistant[PERS_SCORE] < 0 ) { + client->ps.persistant[PERS_SCORE] = 0; + } + } + + // BFP - Team Last Man Standing + if ( g_gametype.integer == GT_TLMS ) { + client->forceToSpectate = qfalse; + } // read or initialize the session data if ( firstTime || level.newSession ) { - G_InitSessionData( client, userinfo ); + value = Info_ValueForKey( userinfo, "team" ); + G_InitSessionData( client, value, isBot ); + G_WriteClientSessionData( client ); } - G_ReadSessionData( client ); + + G_ReadClientSessionData( client ); if( isBot ) { - ent->r.svFlags |= SVF_BOT; - ent->inuse = qtrue; if( !G_BotConnect( clientNum, !firstTime ) ) { return "BotConnectfailed"; } + ent->r.svFlags |= SVF_BOT; + client->sess.spectatorClient = clientNum; } + ent->inuse = qtrue; - // get and distribute relevent paramters + // get and distribute relevant paramters G_LogPrintf( "ClientConnect: %i\n", clientNum ); + + client->pers.connected = CON_CONNECTING; + ClientUserinfoChanged( clientNum ); // don't do the "xxx connected" messages if they were caried over from previous level @@ -934,11 +1079,6 @@ char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); } - if ( g_gametype.integer >= GT_TEAM && - client->sess.sessionTeam != TEAM_SPECTATOR ) { - BroadcastTeamChange( client, -1 ); - } - // count current clients and rank for scoreboard CalculateRanks(); @@ -964,6 +1104,8 @@ void ClientBegin( int clientNum ) { gclient_t *client; gentity_t *tent; int flags; + // BFP - Survival, save scores if the players are spectating + int savedScore; ent = g_entities + clientNum; @@ -980,6 +1122,8 @@ void ClientBegin( int clientNum ) { client->pers.connected = CON_CONNECTED; client->pers.enterTime = level.time; client->pers.teamState.state = TEAM_BEGIN; + // BFP - Survival, save scores if the players are spectating + savedScore = client->ps.persistant[PERS_SCORE]; // save eflags around this, because changing teams will // cause this to happen with a valid entity, and we @@ -990,15 +1134,50 @@ void ClientBegin( int clientNum ) { memset( &client->ps, 0, sizeof( client->ps ) ); client->ps.eFlags = flags; + // BFP - Monster gamemode + if ( level.monsterClientNum == -1 ) { + // first player becomes the monster + level.monsterClientNum = clientNum; + } + if ( clientNum == level.monsterClientNum ) { + client->ps.eFlags |= EF_MONSTER; + } + + // BFP - Survival, save scores if the players are spectating + if ( g_gametype.integer == GT_SURVIVAL ) { + client->ps.persistant[PERS_SCORE] = savedScore; + } + // locate ent at a spawn point ClientSpawn( ent ); + // BFP - Kick the player who uses an illegal player model which isn't in the server +#if KICK_ILLEGAL_PLAYER_MODEL + { + char userinfo[MAX_INFO_STRING]; + char model[MAX_QPATH]; + + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + Q_strncpyz( model, G_GetPlayerModelName( clientNum, userinfo ), sizeof( model ) ); + + if ( !G_PlayerModelExistsOnServer( model ) + && ( g_gametype.integer != GT_MONSTER + || ( g_gametype.integer == GT_MONSTER && g_monster.integer < 1 ) ) ) { + if ( client && client->pers.connected != CON_DISCONNECTED ) { + trap_DropClient( clientNum, "was kicked" ); + } + return; + } + } +#endif + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { // send event tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = ent->s.clientNum; - if ( g_gametype.integer != GT_TOURNAMENT ) { + if ( g_gametype.integer != GT_TOURNAMENT + && g_gametype.integer != GT_SURVIVAL ) { // BFP - Survival trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); } } @@ -1028,6 +1207,9 @@ void ClientSpawn(gentity_t *ent) { gentity_t *spawnPoint; int flags; int savedPing; + // BFP - Team Last Man Standing, save force to spectate and selected team + qboolean savedForcedToSpectate; + team_t savedSelectedTeam; // char *savedAreaBits; int accuracy_hits, accuracy_shots; int eventSequence; @@ -1051,6 +1233,12 @@ void ClientSpawn(gentity_t *ent) { spawn_origin, spawn_angles); } else { do { + // BFP - Monster gamemode, the player monster spawns at the spectator spawn point, kinda curious (¬_¬) + if ( g_gametype.integer == GT_MONSTER + && ( client->ps.eFlags & EF_MONSTER ) ) { + spawnPoint = SelectSpectatorSpawnPoint ( spawn_origin, spawn_angles ); + break; + } // the first spawn should be at a good looking spot if ( !client->pers.initialSpawn && client->pers.localClient ) { client->pers.initialSpawn = qtrue; @@ -1079,9 +1267,6 @@ void ClientSpawn(gentity_t *ent) { } client->pers.teamState.state = TEAM_ACTIVE; - // always clear the kamikaze flag - ent->s.eFlags &= ~EF_KAMIKAZE; - // toggle the teleport bit so the client knows to not lerp // and never clear the voted flag flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); @@ -1091,6 +1276,10 @@ void ClientSpawn(gentity_t *ent) { saved = client->pers; savedSess = client->sess; + // BFP - Team Last Man Standing, save force to spectate and selected team + savedForcedToSpectate = client->forceToSpectate; + savedSelectedTeam = client->selectedTeam; + savedPing = client->ps.ping; // savedAreaBits = client->areabits; accuracy_hits = client->accuracy_hits; @@ -1100,10 +1289,14 @@ void ClientSpawn(gentity_t *ent) { } eventSequence = client->ps.eventSequence; - memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? + Com_Memset (client, 0, sizeof(*client)); client->pers = saved; client->sess = savedSess; + // BFP - Team Last Man Standing, keep force to spectate and selected team + client->forceToSpectate = savedForcedToSpectate; + client->selectedTeam = savedSelectedTeam; + client->ps.ping = savedPing; // client->areabits = savedAreaBits; client->accuracy_hits = accuracy_hits; @@ -1118,18 +1311,23 @@ void ClientSpawn(gentity_t *ent) { client->ps.persistant[PERS_SPAWN_COUNT]++; client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; - client->airOutTime = level.time + 12000; + // BFP - No drowning + // client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); + // BFP - No handicap +#if 0 // set max health client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { client->pers.maxHealth = 100; } - // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; +#endif + client->ps.eFlags = flags; + // clear entity values ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[index]; ent->takedamage = qtrue; @@ -1147,48 +1345,105 @@ void ClientSpawn(gentity_t *ent) { client->ps.clientNum = index; - // TODO: BFP - list of 5 skills + // BFP - TODO: list of 5 skills - client->ps.stats[STAT_KI] = 1000; // BFP - give ki at start + // BFP - Powerlevel start + if ( client->ps.persistant[PERS_POWERLEVEL] < g_basePL.integer ) { + client->ps.persistant[PERS_POWERLEVEL] = g_basePL.integer; + } else { + if ( level.numConnectedClients > 1 ) { + client->ps.persistant[PERS_POWERLEVEL] = ClientGetAveragePowerlevel(); + } + } + if ( client->ps.persistant[PERS_POWERLEVEL] > 1000 || g_basePL.integer > 998 ) { + client->ps.persistant[PERS_POWERLEVEL] = 1000; + } - client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); - client->ps.ammo[WP_MACHINEGUN] = 100; + // BFP - Max spawn powerlevel, only when g_maxSpawnPL is higher than 0 + if ( g_maxSpawnPL.integer > 0 && client->ps.persistant[PERS_POWERLEVEL] > g_maxSpawnPL.integer + && g_basePL.integer < 999 ) { + client->ps.persistant[PERS_POWERLEVEL] = g_maxSpawnPL.integer; + } - client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_ROCKET_LAUNCHER ); - client->ps.ammo[WP_ROCKET_LAUNCHER] = 100; + // BFP - Team Last Man Standing, the powerlevel always starts at the maximum and reset spectating reason + if ( g_gametype.integer == GT_TLMS ) { + client->ps.persistant[PERS_POWERLEVEL] = 1000; + } + if ( client->forceToSpectate <= 0 ) { + client->forceToSpectate = qfalse; + } - client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_PLASMAGUN ); - client->ps.ammo[WP_PLASMAGUN] = 100; - - client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_RAILGUN ); - client->ps.ammo[WP_RAILGUN] = 100; + // BFP - Send powerlevel info to cgame reusing frame from entityState_t struct + ent->s.frame = client->ps.persistant[PERS_POWERLEVEL]; - client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BFG ); - client->ps.ammo[WP_BFG] = 100; + // BFP - Max health start + client->ps.stats[STAT_MAX_HEALTH] = 1 + client->ps.persistant[PERS_POWERLEVEL]; + if ( client->ps.stats[STAT_MAX_HEALTH] > 1000 ) { + client->ps.stats[STAT_MAX_HEALTH] = 1000; + } - // TODO: BFP - Gauntlet must be replaced as a bind key (Alt key), - // it will use fight animation (using kicks and fists) + // BFP - Ki start + client->ps.stats[STAT_KI] = 999; + // BFP - NOTE: What the heck? Did BFP dev make this multiplying 9.00825 with powerlevel? Strange approximation... + client->ps.stats[STAT_KI] = client->ps.stats[STAT_KI] + ( 9.00825 * client->ps.persistant[PERS_POWERLEVEL] ); + client->ps.stats[STAT_MAX_KI] = client->ps.stats[STAT_KI]; - client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); - client->ps.ammo[WP_GAUNTLET] = -1; - client->ps.ammo[WP_GRAPPLING_HOOK] = -1; + if ( client->ps.stats[STAT_MAX_KI] > 10000 ) { + client->ps.stats[STAT_MAX_KI] = client->ps.stats[STAT_KI] = 10000; + } + + // BFP - Monster gamemode + if ( g_gametype.integer == GT_MONSTER && g_monster.integer > 0 + && client->ps.clientNum == level.monsterClientNum ) { + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_GRAPPLING_HOOK ); + client->ps.ammo[WP_GRAPPLING_HOOK] = 100; + } else { + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); + client->ps.ammo[WP_MACHINEGUN] = 100; + + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_ROCKET_LAUNCHER ); + client->ps.ammo[WP_ROCKET_LAUNCHER] = 100; + + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_PLASMAGUN ); + client->ps.ammo[WP_PLASMAGUN] = 100; + + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_RAILGUN ); + client->ps.ammo[WP_RAILGUN] = 100; + + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BFG ); + client->ps.ammo[WP_BFG] = 100; + + //client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); + //client->ps.ammo[WP_GAUNTLET] = -1; + //client->ps.ammo[WP_GRAPPLING_HOOK] = -1; + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK ); + client->ps.ammo[WP_GRAPPLING_HOOK] = 100; + } // health will count down towards max_health - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; // BFP - Before Q3: + 25 - ent->kiamount = client->ps.stats[STAT_KI]; + // BFP - Monster gamemode + if ( g_gametype.integer == GT_MONSTER && client->ps.clientNum == level.monsterClientNum + && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + ClientBecomeMonster( ent ); + } 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; + client->ps.viewheight = DEFAULT_VIEWHEIGHT; + client->ps.gravity = g_gravity.integer; + client->ps.speed = g_speed.integer; - trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + trap_GetUsercmd( client - level.clients, &client->pers.cmd ); SetClientViewAngle( ent, spawn_angles ); if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - + // BFP - Monster gamemode, if this guy spectated, respawn the other guy who can become a monster + ClientCheckMonsterGone( ent ); } else { G_KillBox( ent ); trap_LinkEntity (ent); @@ -1298,13 +1553,17 @@ void ClientDisconnect( int clientNum ) { 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 ) + if ( ( g_gametype.integer == GT_TOURNAMENT + || g_gametype.integer == GT_SURVIVAL ) // BFP - Survival && !level.intermissiontime && !level.warmupTime && level.sortedClients[1] == clientNum ) { level.clients[ level.sortedClients[0] ].sess.wins++; ClientUserinfoChanged( level.sortedClients[0] ); } + // BFP - Monster gamemode, if this guy disconnected, respawn the other guy who can become a monster + ClientCheckMonsterGone( ent ); + trap_UnlinkEntity (ent); ent->s.modelindex = 0; ent->inuse = qfalse; diff --git a/source/game/g_cmds.c b/source/game/g_cmds.c index a219664..d9006ae 100644 --- a/source/game/g_cmds.c +++ b/source/game/g_cmds.c @@ -24,6 +24,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "menudef.h" // for the voice chats +// BFP - BFP tournament allow chat macro +#define BFP_TOURNAMENT_CHAT_NOT_ALLOWED 0 + /* ================== DeathmatchScoreboardMessage @@ -64,6 +67,8 @@ void DeathmatchScoreboardMessage( gentity_t *ent ) { } perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; + // BFP - No impressive, gauntlet, defend, assist and capture counters +#if 0 Com_sprintf (entry, sizeof(entry), " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000, @@ -75,6 +80,18 @@ void DeathmatchScoreboardMessage( gentity_t *ent ) { cl->ps.persistant[PERS_ASSIST_COUNT], perfect, cl->ps.persistant[PERS_CAPTURES]); +#endif + Com_sprintf (entry, sizeof(entry), + " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], + cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000, + scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, + 0, + cl->ps.persistant[PERS_EXCELLENT_COUNT], + 0, + 0, + 0, + perfect, + 0); j = (int)strlen(entry); if (stringlength + j > 1024) break; @@ -264,9 +281,14 @@ void Cmd_Give_f (gentity_t *ent) if (give_all || Q_stricmp(name, "ammo") == 0) { + // BFP - TODO: Don't give all ammo to all weapons, that part is supposed to be removed in the future +#if 1 for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { ent->client->ps.ammo[i] = 999; } +#endif + ent->client->ps.stats[STAT_KI] = ent->client->ps.stats[STAT_MAX_KI]; + if (!give_all) return; } @@ -283,6 +305,8 @@ void Cmd_Give_f (gentity_t *ent) ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; return; } + // BFP - No impressive, gauntlet, defend and assist counters +#if 0 if (Q_stricmp(name, "impressive") == 0) { ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; return; @@ -299,6 +323,7 @@ void Cmd_Give_f (gentity_t *ent) ent->client->ps.persistant[PERS_ASSIST_COUNT]++; return; } +#endif // spawn a specific item right on the player if ( !give_all ) { @@ -571,7 +596,8 @@ void SetTeam( gentity_t *ent, char *s ) { } // override decision if limiting the players - if ( (g_gametype.integer == GT_TOURNAMENT) + if ( ( g_gametype.integer == GT_TOURNAMENT + || g_gametype.integer == GT_SURVIVAL ) // BFP - Survival && level.numNonSpectatorClients >= 2 ) { team = TEAM_SPECTATOR; } else if ( g_maxGameClients.integer > 0 && @@ -579,6 +605,13 @@ void SetTeam( gentity_t *ent, char *s ) { team = TEAM_SPECTATOR; } + // BFP - Monster gamemode, check if the player monster is changing teams + if ( g_gametype.integer == GT_MONSTER + && ( ent->client->ps.eFlags & EF_MONSTER ) + && team == TEAM_SPECTATOR ) { + ClientCheckMonsterGone( ent ); + } + // // decide if we will allow the change // @@ -644,12 +677,45 @@ to free floating spectator mode ================= */ void StopFollowing( gentity_t *ent ) { - ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; - ent->client->sess.sessionTeam = TEAM_SPECTATOR; - ent->client->sess.spectatorState = SPECTATOR_FREE; - ent->client->ps.pm_flags &= ~PMF_FOLLOW; - ent->r.svFlags &= ~SVF_BOT; - ent->client->ps.clientNum = ent - g_entities; + gclient_t *client; + + if ( ( ent->r.svFlags & SVF_BOT ) || !ent->inuse ) + return; + + client = ent->client; + + client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; + client->sess.sessionTeam = TEAM_SPECTATOR; + SetClientViewAngle( ent, client->ps.viewangles ); + + client->sess.spectatorState = SPECTATOR_FREE; + client->ps.pm_flags &= ~PMF_FOLLOW; + //ent->r.svFlags &= ~SVF_BOT; + + client->ps.clientNum = ent - g_entities; +} + + +/* +================= +StopAndSpectateFreely + +Stops following and spectate freely +================= +*/ +static void StopAndSpectateFreely( gentity_t *ent ) { // BFP - Stops following and spectate freely + vec3_t currentOrigin, currentAngles; + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + // keep the origin and angles after stop following + VectorCopy( ent->client->ps.origin, currentOrigin ); + currentOrigin[2] += 20; + VectorCopy( ent->client->ps.viewangles, currentAngles ); + + StopFollowing( ent ); + + VectorCopy( currentOrigin, ent->client->ps.origin ); + VectorCopy( currentAngles, ent->client->ps.viewangles ); + } } /* @@ -680,22 +746,76 @@ void Cmd_Team_f( gentity_t *ent ) { return; } + // BFP - No switch team time delay +#if BFP_NO_SWITCH_TEAM_TIME if ( ent->client->switchTeamTime > level.time ) { trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" ); return; } +#endif // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) + if ( ( g_gametype.integer == GT_TOURNAMENT + || g_gametype.integer == GT_SURVIVAL ) // BFP - Survival && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } trap_Argv( 1, s, sizeof( s ) ); + // BFP - Kick/force to spectate the player who uses an illegal model which isn't available in the server + { + char model[MAX_QPATH]; + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo( ent->client->ps.clientNum, userinfo, sizeof( userinfo ) ); + Q_strncpyz( model, G_GetPlayerModelName( ent->client->ps.clientNum, userinfo ), sizeof( model ) ); + + if ( !G_PlayerModelExistsOnServer( model ) + && ( g_gametype.integer != GT_MONSTER + || ( g_gametype.integer == GT_MONSTER && g_monster.integer < 1 ) ) ) { +#if KICK_ILLEGAL_PLAYER_MODEL + if ( ent->client && ent->client->pers.connected != CON_DISCONNECTED ) { + trap_DropClient( ent->client->ps.clientNum, "was kicked" ); + } +#else + gentity_t *tempEnt = G_TempEntity( ent->r.currentOrigin, EV_OBITUARY ); + tempEnt->s.eventParm = MOD_ILLEGAL_PLAYER_MODEL; + tempEnt->r.svFlags = SVF_BROADCAST; + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->sess.spectatorClient = 0; + ClientBegin( ent->client->ps.clientNum ); +#endif + return; + } + } + + // BFP - Team Last Man Standing, show a centerprint message to switching teams when the player were fragged and forced to spectate + if ( g_gametype.integer == GT_TLMS + && ent->client->forceToSpectate ) { + trap_SendServerCommand( ent->client->ps.clientNum, + "cp \"You are eliminated!\nWait until the round ends to respawn with your team!\n\"" ); + return; + } + + // BFP - Team Last Man Standing, show a centerprint message to switching teams when joined + if ( g_gametype.integer == GT_TLMS + && ( ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE ) + && ent->client->sess.sessionTeam != TEAM_SPECTATOR + && !ent->client->forceToSpectate ) { + trap_SendServerCommand( ent->client->ps.clientNum, + "cp \"You cannot switch teams during the round!\n\"" + ); + return; + } + SetTeam( ent, s ); + // BFP - No switch team time delay +#if BFP_NO_SWITCH_TEAM_TIME ent->client->switchTeamTime = level.time + 5000; +#endif } @@ -732,7 +852,8 @@ void Cmd_Follow_f( gentity_t *ent ) { } // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) + if ( ( g_gametype.integer == GT_TOURNAMENT + || g_gametype.integer == GT_SURVIVAL ) // BFP - Survival && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } @@ -756,7 +877,8 @@ void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { int original; // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) + if ( ( g_gametype.integer == GT_TOURNAMENT + || g_gametype.integer == GT_SURVIVAL ) // BFP - Survival && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } @@ -769,15 +891,24 @@ void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { G_Error( "Cmd_FollowCycle_f: bad dir %i", dir ); } - clientnum = ent->client->sess.spectatorClient; + // BFP - If currently in free spectator mode, start following from the beginning + if ( ent->client->sess.spectatorState == SPECTATOR_FREE ) { + clientnum = ( dir == 1 ) ? -1 : level.maxclients; + } else { + clientnum = ent->client->sess.spectatorClient; + } original = clientnum; do { clientnum += dir; if ( clientnum >= level.maxclients ) { - clientnum = 0; + // BFP - Reached the end going forward - stop following + StopAndSpectateFreely( ent ); + return; } if ( clientnum < 0 ) { - clientnum = level.maxclients - 1; + // BFP - Reached the beginning going backward - stop following + StopAndSpectateFreely( ent ); + return; } // can only follow connected clients @@ -822,12 +953,15 @@ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, cons if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { return; } + // BFP - Chatting is allowed on BFP tournaments +#if BFP_TOURNAMENT_CHAT_NOT_ALLOWED // 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; } +#endif trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", mode == SAY_TEAM ? "tchat" : "chat", @@ -845,6 +979,12 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) char text[MAX_SAY_TEXT]; char location[64]; + // BFP - Allow spectator chat + if ( ent->client->ps.pm_type == PM_SPECTATOR + && g_allowSpectatorChat.integer <= 0 ) { + return; + } + if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { mode = SAY_ALL; } @@ -975,10 +1115,13 @@ static void G_VoiceTo( gentity_t *ent, gentity_t *other, int mode, const char *i if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { return; } + // BFP - Chatting is allowed on BFP tournaments +#if BFP_TOURNAMENT_CHAT_NOT_ALLOWED // no chatting to players in tournements if ( (g_gametype.integer == GT_TOURNAMENT )) { return; } +#endif if (mode == SAY_TEAM) { color = COLOR_CYAN; @@ -1200,11 +1343,11 @@ static const char *gameNames[] = { "Free For All", "Tournament", "Single Player", + "Survival", + "Monster", "Team Deathmatch", - "Capture the Flag", - "One Flag CTF", - "Overload", - "Harvester" + "Team Last Man Standing", + "Capture the Flag" }; /* @@ -1253,9 +1396,14 @@ void Cmd_CallVote_f( gentity_t *ent ) { } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { } else if ( !Q_stricmp( arg1, "timelimit" ) ) { } else if ( !Q_stricmp( arg1, "fraglimit" ) ) { + } else if ( !Q_stricmp( arg1, "g_basePL" ) ) { // BFP - Base powerlevel + } else if ( !Q_stricmp( arg1, "g_hitStun" ) ) { // BFP - Hit stun + } else if ( !Q_stricmp( arg1, "g_allowSpectatorChat" ) ) { // BFP - Allow spectator chat + } else if ( !Q_stricmp( arg1, "g_meleeOnly" ) ) { // BFP - Melee only + } else if ( !Q_stricmp( arg1, "g_noFlight" ) ) { // BFP - No flight } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); - trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit