summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIronClawTrem <louie.nutman@gmail.com>2019-08-06 02:17:23 +0100
committerIronClawTrem <louie.nutman@gmail.com>2019-08-06 02:17:23 +0100
commite5ff1d3d4bc01d98b12d05f9cb85457c7f15c424 (patch)
treea18bc2026e95b76f820170855ea3a39aa5f03324
first commit
-rw-r--r--CC229
-rw-r--r--COPYING24
-rw-r--r--GNUmakefile1287
-rw-r--r--GPL281
-rw-r--r--README.md7
-rw-r--r--src/cgame/cg_animation.c111
-rw-r--r--src/cgame/cg_animmapobj.c227
-rw-r--r--src/cgame/cg_attachment.c404
-rw-r--r--src/cgame/cg_buildable.c1423
-rw-r--r--src/cgame/cg_consolecmds.c325
-rw-r--r--src/cgame/cg_draw.c3515
-rw-r--r--src/cgame/cg_drawtools.c378
-rw-r--r--src/cgame/cg_ents.c1256
-rw-r--r--src/cgame/cg_event.c1034
-rw-r--r--src/cgame/cg_local.h2207
-rw-r--r--src/cgame/cg_main.c2121
-rw-r--r--src/cgame/cg_marks.c289
-rw-r--r--src/cgame/cg_mem.c202
-rw-r--r--src/cgame/cg_particles.c2567
-rw-r--r--src/cgame/cg_players.c2565
-rw-r--r--src/cgame/cg_playerstate.c317
-rw-r--r--src/cgame/cg_predict.c879
-rw-r--r--src/cgame/cg_ptr.c81
-rw-r--r--src/cgame/cg_public.h273
-rw-r--r--src/cgame/cg_scanner.c365
-rw-r--r--src/cgame/cg_servercmds.c1016
-rw-r--r--src/cgame/cg_snapshot.c464
-rw-r--r--src/cgame/cg_syscalls.asm121
-rw-r--r--src/cgame/cg_syscalls.c574
-rw-r--r--src/cgame/cg_syscalls_11.asm115
-rw-r--r--src/cgame/cg_trails.c1500
-rw-r--r--src/cgame/cg_tutorial.c664
-rw-r--r--src/cgame/cg_view.c1338
-rw-r--r--src/cgame/cg_weapons.c1847
-rw-r--r--src/client/keycodes.h278
-rw-r--r--src/game/bg_lib.c2045
-rw-r--r--src/game/bg_lib.h120
-rw-r--r--src/game/bg_local.h88
-rw-r--r--src/game/bg_misc.c5687
-rw-r--r--src/game/bg_pmove.c3540
-rw-r--r--src/game/bg_public.h1330
-rw-r--r--src/game/bg_slidemove.c416
-rw-r--r--src/game/g_active.c2033
-rw-r--r--src/game/g_admin.c7530
-rw-r--r--src/game/g_admin.h276
-rw-r--r--src/game/g_buildable.c4709
-rw-r--r--src/game/g_client.c2044
-rw-r--r--src/game/g_cmds.c5760
-rw-r--r--src/game/g_combat.c1758
-rw-r--r--src/game/g_local.h1533
-rw-r--r--src/game/g_main.c3003
-rw-r--r--src/game/g_maprotation.c1318
-rw-r--r--src/game/g_mem.c216
-rw-r--r--src/game/g_misc.c436
-rw-r--r--src/game/g_missile.c810
-rw-r--r--src/game/g_mover.c2479
-rw-r--r--src/game/g_physics.c167
-rw-r--r--src/game/g_ptr.c143
-rw-r--r--src/game/g_public.h267
-rw-r--r--src/game/g_session.c171
-rw-r--r--src/game/g_spawn.c698
-rw-r--r--src/game/g_svcmds.c751
-rw-r--r--src/game/g_syscalls.asm68
-rw-r--r--src/game/g_syscalls.c284
-rw-r--r--src/game/g_target.c478
-rw-r--r--src/game/g_team.c276
-rw-r--r--src/game/g_trigger.c1141
-rw-r--r--src/game/g_utils.c849
-rw-r--r--src/game/g_weapon.c1661
-rw-r--r--src/game/tremulous.h626
-rw-r--r--src/qcommon/q_math.c1562
-rw-r--r--src/qcommon/q_platform.h386
-rw-r--r--src/qcommon/q_shared.c1347
-rw-r--r--src/qcommon/q_shared.h1342
-rw-r--r--src/qcommon/qfiles.h626
-rw-r--r--src/qcommon/surfaceflags.h91
-rw-r--r--src/renderer/tr_types.h234
-rw-r--r--src/tools/asm/README.Id10
-rw-r--r--src/tools/asm/cmdlib.c1141
-rw-r--r--src/tools/asm/cmdlib.h153
-rw-r--r--src/tools/asm/lib.txt31
-rw-r--r--src/tools/asm/mathlib.h95
-rw-r--r--src/tools/asm/notes.txt16
-rw-r--r--src/tools/asm/ops.txt132
-rw-r--r--src/tools/asm/opstrings.h176
-rw-r--r--src/tools/asm/q3asm.c1648
-rw-r--r--src/tools/lcc/COPYRIGHT61
-rw-r--r--src/tools/lcc/LOG91
-rw-r--r--src/tools/lcc/README21
-rw-r--r--src/tools/lcc/README.id3
-rw-r--r--src/tools/lcc/cpp/cpp.c326
-rw-r--r--src/tools/lcc/cpp/cpp.h166
-rw-r--r--src/tools/lcc/cpp/eval.c524
-rw-r--r--src/tools/lcc/cpp/getopt.c53
-rw-r--r--src/tools/lcc/cpp/hideset.c112
-rw-r--r--src/tools/lcc/cpp/include.c153
-rw-r--r--src/tools/lcc/cpp/lex.c580
-rw-r--r--src/tools/lcc/cpp/macro.c514
-rw-r--r--src/tools/lcc/cpp/nlist.c104
-rw-r--r--src/tools/lcc/cpp/tokens.c370
-rw-r--r--src/tools/lcc/cpp/unix.c134
-rw-r--r--src/tools/lcc/doc/4.html754
-rw-r--r--src/tools/lcc/doc/bprint.183
-rw-r--r--src/tools/lcc/doc/bprint.pdfbin0 -> 4963 bytes
-rw-r--r--src/tools/lcc/doc/install.html796
-rw-r--r--src/tools/lcc/doc/lcc.1605
-rw-r--r--src/tools/lcc/doc/lcc.pdfbin0 -> 16421 bytes
-rw-r--r--src/tools/lcc/etc/bytecode.c70
-rw-r--r--src/tools/lcc/etc/lcc.c857
-rw-r--r--src/tools/lcc/lburg/gram.c677
-rw-r--r--src/tools/lcc/lburg/gram.y202
-rw-r--r--src/tools/lcc/lburg/lburg.1179
-rw-r--r--src/tools/lcc/lburg/lburg.c671
-rw-r--r--src/tools/lcc/lburg/lburg.h65
-rw-r--r--src/tools/lcc/src/alloc.c94
-rw-r--r--src/tools/lcc/src/bind.c8
-rw-r--r--src/tools/lcc/src/bytecode.c366
-rw-r--r--src/tools/lcc/src/c.h730
-rw-r--r--src/tools/lcc/src/config.h102
-rw-r--r--src/tools/lcc/src/dag.c736
-rw-r--r--src/tools/lcc/src/dagcheck.md210
-rw-r--r--src/tools/lcc/src/decl.c1162
-rw-r--r--src/tools/lcc/src/enode.c544
-rw-r--r--src/tools/lcc/src/error.c137
-rw-r--r--src/tools/lcc/src/event.c28
-rw-r--r--src/tools/lcc/src/expr.c711
-rw-r--r--src/tools/lcc/src/gen.c830
-rw-r--r--src/tools/lcc/src/init.c318
-rw-r--r--src/tools/lcc/src/inits.c7
-rw-r--r--src/tools/lcc/src/input.c135
-rw-r--r--src/tools/lcc/src/lex.c923
-rw-r--r--src/tools/lcc/src/list.c56
-rw-r--r--src/tools/lcc/src/main.c225
-rw-r--r--src/tools/lcc/src/null.c74
-rw-r--r--src/tools/lcc/src/output.c135
-rw-r--r--src/tools/lcc/src/prof.c228
-rw-r--r--src/tools/lcc/src/profio.c276
-rw-r--r--src/tools/lcc/src/simp.c587
-rw-r--r--src/tools/lcc/src/stmt.c696
-rw-r--r--src/tools/lcc/src/string.c122
-rw-r--r--src/tools/lcc/src/sym.c314
-rw-r--r--src/tools/lcc/src/symbolic.c494
-rw-r--r--src/tools/lcc/src/token.h133
-rw-r--r--src/tools/lcc/src/trace.c181
-rw-r--r--src/tools/lcc/src/tree.c223
-rw-r--r--src/tools/lcc/src/types.c748
-rw-r--r--src/ui/menudef.h363
-rw-r--r--src/ui/ui_atoms.c556
-rw-r--r--src/ui/ui_gameinfo.c333
-rw-r--r--src/ui/ui_local.h1210
-rw-r--r--src/ui/ui_main.c6530
-rw-r--r--src/ui/ui_players.c1369
-rw-r--r--src/ui/ui_public.h210
-rw-r--r--src/ui/ui_shared.c6115
-rw-r--r--src/ui/ui_shared.h455
-rw-r--r--src/ui/ui_syscalls.asm102
-rw-r--r--src/ui/ui_syscalls.c387
-rw-r--r--src/ui/ui_syscalls_11.asm98
-rw-r--r--ui/assets/alien/buildstat.cfg37
-rw-r--r--ui/assets/alien/buildstat/frame.tgabin0 -> 1724 bytes
-rw-r--r--ui/assets/alien/buildstat/mark.tgabin0 -> 14180 bytes
-rw-r--r--ui/assets/alien/buildstat/nopower.tgabin0 -> 21285 bytes
-rw-r--r--ui/assets/alien/buildstat/overlay.tgabin0 -> 25069 bytes
-rw-r--r--ui/assets/human/buildstat.cfg39
-rw-r--r--ui/assets/human/buildstat/frame.tgabin0 -> 1724 bytes
-rw-r--r--ui/assets/human/buildstat/mark.tgabin0 -> 14180 bytes
-rw-r--r--ui/assets/human/buildstat/nopower.tgabin0 -> 19244 bytes
-rw-r--r--ui/drop.menu126
-rw-r--r--ui/ingame.menu115
-rw-r--r--ui/ingame.txt8
-rw-r--r--ui/ingame_game.menu3206
-rw-r--r--ui/ingame_options.menu2180
-rw-r--r--ui/joinserver.menu687
-rw-r--r--ui/main.menu163
-rw-r--r--ui/menudef.h363
-rw-r--r--ui/menus.txt20
-rw-r--r--ui/options.menu287
-rw-r--r--ui/quitcredit.menu430
-rw-r--r--ui/say.menu91
-rw-r--r--ui/teamscore.menu305
-rw-r--r--ui/tremulous.txt21
-rw-r--r--ui/tremulous_alien_builder_hud.menu371
-rw-r--r--ui/tremulous_alien_general_hud.menu360
-rw-r--r--ui/tremulous_default_hud.menu165
-rw-r--r--ui/tremulous_human_hud.menu462
185 files changed, 140853 insertions, 0 deletions
diff --git a/CC b/CC
new file mode 100644
index 0000000..92e4148
--- /dev/null
+++ b/CC
@@ -0,0 +1,229 @@
+ Creative_Commons
+ Creative Commons Legal Code
+ Attribution-ShareAlike 2.5
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL
+SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT
+RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS.
+CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND
+DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+License
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
+COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
+COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
+BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS
+CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+CONDITIONS.
+1. Definitions
+
+ a. "Collective Work" means a work, such as a periodical issue, anthology or
+ encyclopedia, in which the Work in its entirety in unmodified form, along
+ with a number of other contributions, constituting separate and
+ independent works in themselves, are assembled into a collective whole. A
+ work that constitutes a Collective Work will not be considered a
+ Derivative Work (as defined below) for the purposes of this License.
+ b. "Derivative Work" means a work based upon the Work or upon the Work and
+ other pre-existing works, such as a translation, musical arrangement,
+ dramatization, fictionalization, motion picture version, sound recording,
+ art reproduction, abridgment, condensation, or any other form in which the
+ Work may be recast, transformed, or adapted, except that a work that
+ constitutes a Collective Work will not be considered a Derivative Work for
+ the purpose of this License. For the avoidance of doubt, where the Work is
+ a musical composition or sound recording, the synchronization of the Work
+ in timed-relation with a moving image ("synching") will be considered a
+ Derivative Work for the purpose of this License.
+ c. "Licensor" means the individual or entity that offers the Work under the
+ terms of this License.
+ d. "Original Author" means the individual or entity who created the Work.
+ e. "Work" means the copyrightable work of authorship offered under the terms
+ of this License.
+ f. "You" means an individual or entity exercising rights under this License
+ who has not previously violated the terms of this License with respect to
+ the Work, or who has received express permission from the Licensor to
+ exercise rights under this License despite a previous violation.
+ g. "License Elements" means the following high-level license attributes as
+ selected by Licensor and indicated in the title of this License:
+ Attribution, ShareAlike.
+
+2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or
+restrict any rights arising from fair use, first sale or other limitations on
+the exclusive rights of the copyright owner under copyright law or other
+applicable laws.
+3. License Grant. Subject to the terms and conditions of this License, Licensor
+hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the
+duration of the applicable copyright) license to exercise the rights in the
+Work as stated below:
+
+ a. to reproduce the Work, to incorporate the Work into one or more Collective
+ Works, and to reproduce the Work as incorporated in the Collective Works;
+ b. to create and reproduce Derivative Works;
+ c. to distribute copies or phonorecords of, display publicly, perform
+ publicly, and perform publicly by means of a digital audio transmission
+ the Work including as incorporated in Collective Works;
+ d. to distribute copies or phonorecords of, display publicly, perform
+ publicly, and perform publicly by means of a digital audio transmission
+ Derivative Works.
+ e. For the avoidance of doubt, where the work is a musical composition:
+
+ i. Performance Royalties Under Blanket Licenses. Licensor waives the
+ exclusive right to collect, whether individually or via a performance
+ rights society (e.g. ASCAP, BMI, SESAC), royalties for the public
+ performance or public digital performance (e.g. webcast) of the Work.
+ ii. Mechanical Rights and Statutory Royalties. Licensor waives the
+ exclusive right to collect, whether individually or via a music
+ rights society or designated agent (e.g. Harry Fox Agency), royalties
+ for any phonorecord You create from the Work ("cover version") and
+ distribute, subject to the compulsory license created by 17 USC
+ Section 115 of the US Copyright Act (or the equivalent in other
+ jurisdictions).
+
+ f. Webcasting Rights and Statutory Royalties. For the avoidance of doubt,
+ where the Work is a sound recording, Licensor waives the exclusive right
+ to collect, whether individually or via a performance-rights society (e.g.
+ SoundExchange), royalties for the public digital performance (e.g.
+ webcast) of the Work, subject to the compulsory license created by 17 USC
+ Section 114 of the US Copyright Act (or the equivalent in other
+ jurisdictions).
+
+The above rights may be exercised in all media and formats whether now known or
+hereafter devised. The above rights include the right to make such
+modifications as are technically necessary to exercise the rights in other
+media and formats. All rights not expressly granted by Licensor are hereby
+reserved.
+4. Restrictions.The license granted in Section 3 above is expressly made
+subject to and limited by the following restrictions:
+
+ a. You may distribute, publicly display, publicly perform, or publicly
+ digitally perform the Work only under the terms of this License, and You
+ must include a copy of, or the Uniform Resource Identifier for, this
+ License with every copy or phonorecord of the Work You distribute,
+ publicly display, publicly perform, or publicly digitally perform. You may
+ not offer or impose any terms on the Work that alter or restrict the terms
+ of this License or the recipients' exercise of the rights granted
+ hereunder. You may not sublicense the Work. You must keep intact all
+ notices that refer to this License and to the disclaimer of warranties.
+ You may not distribute, publicly display, publicly perform, or publicly
+ digitally perform the Work with any technological measures that control
+ access or use of the Work in a manner inconsistent with the terms of this
+ License Agreement. The above applies to the Work as incorporated in a
+ Collective Work, but this does not require the Collective Work apart from
+ the Work itself to be made subject to the terms of this License. If You
+ create a Collective Work, upon notice from any Licensor You must, to the
+ extent practicable, remove from the Collective Work any credit as required
+ by clause 4(c), as requested. If You create a Derivative Work, upon notice
+ from any Licensor You must, to the extent practicable, remove from the
+ Derivative Work any credit as required by clause 4(c), as requested.
+ b. You may distribute, publicly display, publicly perform, or publicly
+ digitally perform a Derivative Work only under the terms of this License,
+ a later version of this License with the same License Elements as this
+ License, or a Creative Commons iCommons license that contains the same
+ License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan).
+ You must include a copy of, or the Uniform Resource Identifier for, this
+ License or other license specified in the previous sentence with every
+ copy or phonorecord of each Derivative Work You distribute, publicly
+ display, publicly perform, or publicly digitally perform. You may not
+ offer or impose any terms on the Derivative Works that alter or restrict
+ the terms of this License or the recipients' exercise of the rights
+ granted hereunder, and You must keep intact all notices that refer to this
+ License and to the disclaimer of warranties. You may not distribute,
+ publicly display, publicly perform, or publicly digitally perform the
+ Derivative Work with any technological measures that control access or use
+ of the Work in a manner inconsistent with the terms of this License
+ Agreement. The above applies to the Derivative Work as incorporated in a
+ Collective Work, but this does not require the Collective Work apart from
+ the Derivative Work itself to be made subject to the terms of this
+ License.
+ c. If you distribute, publicly display, publicly perform, or publicly
+ digitally perform the Work or any Derivative Works or Collective Works,
+ You must keep intact all copyright notices for the Work and provide,
+ reasonable to the medium or means You are utilizing: (i) the name of the
+ Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if
+ the Original Author and/or Licensor designate another party or parties
+ (e.g. a sponsor institute, publishing entity, journal) for attribution in
+ Licensor's copyright notice, terms of service or by other reasonable
+ means, the name of such party or parties; the title of the Work if
+ supplied; to the extent reasonably practicable, the Uniform Resource
+ Identifier, if any, that Licensor specifies to be associated with the
+ Work, unless such URI does not refer to the copyright notice or licensing
+ information for the Work; and in the case of a Derivative Work, a credit
+ identifying the use of the Work in the Derivative Work (e.g., "French
+ translation of the Work by Original Author," or "Screenplay based on
+ original Work by Original Author"). Such credit may be implemented in any
+ reasonable manner; provided, however, that in the case of a Derivative
+ Work or Collective Work, at a minimum such credit will appear where any
+ other comparable authorship credit appears and in a manner at least as
+ prominent as such other comparable authorship credit.
+
+5. Representations, Warranties and Disclaimer
+UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK
+AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE
+MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
+LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR
+PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY,
+OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME
+JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH
+EXCLUSION MAY NOT APPLY TO YOU.
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN
+NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL,
+INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS
+LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+7. Termination
+
+ a. This License and the rights granted hereunder will terminate automatically
+ upon any breach by You of the terms of this License. Individuals or
+ entities who have received Derivative Works or Collective Works from You
+ under this License, however, will not have their licenses terminated
+ provided such individuals or entities remain in full compliance with those
+ licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of
+ this License.
+ b. Subject to the above terms and conditions, the license granted here is
+ perpetual (for the duration of the applicable copyright in the Work).
+ Notwithstanding the above, Licensor reserves the right to release the Work
+ under different license terms or to stop distributing the Work at any
+ time; provided, however that any such election will not serve to withdraw
+ this License (or any other license that has been, or is required to be,
+ granted under the terms of this License), and this License will continue
+ in full force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+ a. Each time You distribute or publicly digitally perform the Work or a
+ Collective Work, the Licensor offers to the recipient a license to the
+ Work on the same terms and conditions as the license granted to You under
+ this License.
+ b. Each time You distribute or publicly digitally perform a Derivative Work,
+ Licensor offers to the recipient a license to the original Work on the
+ same terms and conditions as the license granted to You under this
+ License.
+ c. If any provision of this License is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of the
+ remainder of the terms of this License, and without further action by the
+ parties to this agreement, such provision shall be reformed to the minimum
+ extent necessary to make such provision valid and enforceable.
+ d. No term or provision of this License shall be deemed waived and no breach
+ consented to unless such waiver or consent shall be in writing and signed
+ by the party to be charged with such waiver or consent.
+ e. This License constitutes the entire agreement between the parties with
+ respect to the Work licensed here. There are no understandings, agreements
+ or representations with respect to the Work not specified here. Licensor
+ shall not be bound by any additional provisions that may appear in any
+ communication from You. This License may not be modified without the
+ mutual written agreement of the Licensor and You.
+
+Creative Commons is not a party to this License, and makes no warranty
+whatsoever in connection with the Work. Creative Commons will not be liable to
+You or any party on any legal theory for any damages whatsoever, including
+without limitation any general, special, incidental or consequential damages
+arising in connection to this license. Notwithstanding the foregoing two (2)
+sentences, if Creative Commons has expressly identified itself as the Licensor
+hereunder, it shall have all rights and obligations of Licensor.
+Except for the limited purpose of indicating to the public that the Work is
+licensed under the CCPL, neither party will use the trademark "Creative
+Commons" or any related trademark or logo of Creative Commons without the prior
+written consent of Creative Commons. Any permitted use will be in compliance
+with Creative Commons' then-current trademark usage guidelines, as may be
+published on its website or otherwise made available upon request from time to
+time.
+Creative Commons may be contacted at http://creativecommons.org/.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..88fea84
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,24 @@
+---------------------------------------------------------- Tremulous License ---
+
+Tremulous is licensed in two broadly separate sections: the code and the media.
+
+The code is licensed under the GNU GENERAL PUBLIC LICENSE. This license is
+contained in full in the file named GPL. Please be aware of the exceptions to
+this license as listed below.
+
+The media is licensed under the CREATIVE COMMONS ATTRIBUTION-SHAREALIKE 2.5
+LICENSE. Please read http://creativecommons.org/licenses/by-sa/2.5/ to learn
+more about this license. The full license text is contained in the file named
+CC.
+
+
+---------------------------------------------------- Code License Exceptions ---
+
+The following files contain sections of code that are not licensed under the
+GPL, but are nevertheless GPL compatible. The license text for these licenses
+is contained within the files as listed.
+
+ src/qcommon/unzip.c zlib license
+ src/game/bg_lib.c BSD license
+ src/client/snd_adpcm.c Stichting Mathematisch Centrum license
+ src/jpeg-6/* JPEG license
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..772a9de
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,1287 @@
+#
+# Tremulous Makefile
+#
+# GNU Make required
+#
+
+COMPILE_PLATFORM=$(shell uname | sed -e 's/_.*//' | tr '[:upper:]' '[:lower:]' | sed -e 's/\//_/g')
+COMPILE_ARCH=$(shell uname -m | sed -e 's/i.86/x86/' | sed -e 's/^arm.*/arm/')
+
+ifeq ($(COMPILE_PLATFORM),sunos)
+ # Solaris uname and GNU uname differ
+ COMPILE_ARCH=$(shell uname -p | sed -e 's/i.86/x86/')
+endif
+
+ifndef BUILD_GAME_SO
+ BUILD_GAME_SO = 0
+endif
+ifndef BUILD_GAME_QVM
+ BUILD_GAME_QVM = 1
+endif
+ifndef BUILD_GAME_QVM_11
+ BUILD_GAME_QVM_11= 1
+endif
+ifndef BUILD_ONLY_GAME
+ BUILD_ONLY_GAME = 0
+endif
+ifndef BUILD_ONLY_CGUI
+ BUILD_ONLY_CGUI = 0
+endif
+
+#############################################################################
+#
+# If you require a different configuration from the defaults below, create a
+# new file named "Makefile.local" in the same directory as this file and define
+# your parameters there. This allows you to change configuration without
+# causing problems with keeping up to date with the repository.
+#
+#############################################################################
+-include GNUmakefile.local
+
+include $(SETTINGS_MAKEFILES)
+
+ifeq ($(COMPILE_PLATFORM),cygwin)
+ PLATFORM=mingw32
+endif
+
+ifndef PLATFORM
+PLATFORM=$(COMPILE_PLATFORM)
+endif
+export PLATFORM
+
+ifeq ($(COMPILE_ARCH),i86pc)
+ COMPILE_ARCH=x86
+endif
+
+ifeq ($(COMPILE_ARCH),amd64)
+ COMPILE_ARCH=x86_64
+endif
+ifeq ($(COMPILE_ARCH),x64)
+ COMPILE_ARCH=x86_64
+endif
+
+ifeq ($(COMPILE_ARCH),powerpc)
+ COMPILE_ARCH=ppc
+endif
+ifeq ($(COMPILE_ARCH),powerpc64)
+ COMPILE_ARCH=ppc64
+endif
+
+ifeq ($(COMPILE_ARCH),axp)
+ COMPILE_ARCH=alpha
+endif
+
+ifndef ARCH
+ARCH=$(COMPILE_ARCH)
+endif
+export ARCH
+
+ifneq ($(PLATFORM),$(COMPILE_PLATFORM))
+ CROSS_COMPILING=1
+else
+ CROSS_COMPILING=0
+
+ ifneq ($(ARCH),$(COMPILE_ARCH))
+ CROSS_COMPILING=1
+ endif
+endif
+export CROSS_COMPILING
+
+ifndef VERSION
+VERSION=slackers
+endif
+
+ifndef CLIENTBIN
+CLIENTBIN=tremulous
+endif
+
+ifndef BASEGAME
+BASEGAME=slacker
+endif
+
+ifndef BASEGAME_CFLAGS
+BASEGAME_CFLAGS=
+endif
+
+ifndef COPYDIR
+COPYDIR="/usr/local/games/tremulous"
+endif
+
+ifndef COPYBINDIR
+COPYBINDIR=$(COPYDIR)
+endif
+
+ifndef MOUNT_DIR
+MOUNT_DIR=src
+endif
+
+ifndef ASSETS_DIR
+ASSETS_DIR=assets
+endif
+
+ifndef BUILD_DIR
+BUILD_DIR=bld
+endif
+
+ifndef TEMPDIR
+TEMPDIR=/tmp
+endif
+
+ifndef DEBUG_CFLAGS
+DEBUG_CFLAGS=-g -O0
+endif
+
+#############################################################################
+
+BD=$(BUILD_DIR) # /debug-$(PLATFORM)-$(ARCH)
+BR=$(BUILD_DIR) # /release-$(PLATFORM)-$(ARCH)
+CDIR=$(MOUNT_DIR)/client
+SDIR=$(MOUNT_DIR)/server
+CMDIR=$(MOUNT_DIR)/qcommon
+GDIR=$(MOUNT_DIR)/game
+CGDIR=$(MOUNT_DIR)/cgame
+NDIR=$(MOUNT_DIR)/null
+UIDIR=$(MOUNT_DIR)/ui
+Q3ASMDIR=$(MOUNT_DIR)/tools/asm
+LBURGDIR=$(MOUNT_DIR)/tools/lcc/lburg
+Q3CPPDIR=$(MOUNT_DIR)/tools/lcc/cpp
+Q3LCCETCDIR=$(MOUNT_DIR)/tools/lcc/etc
+Q3LCCSRCDIR=$(MOUNT_DIR)/tools/lcc/src
+TEMPDIR=/tmp
+
+# Add git version info
+USE_GIT=
+ifeq ($(wildcard .git),.git)
+ GIT_REV=$(shell git describe --tag)
+ ifneq ($(GIT_REV),)
+ VERSION:=$(GIT_REV)
+ USE_GIT=1
+ endif
+endif
+
+#############################################################################
+# SETUP AND BUILD -- LINUX
+#############################################################################
+
+## Defaults
+LIB=lib
+
+INSTALL=install
+MKDIR=mkdir
+
+ifneq (,$(findstring "$(PLATFORM)", "linux" "gnu_kfreebsd" "kfreebsd-gnu"))
+
+ ifeq ($(ARCH),x86_64)
+ LIB=lib64
+ else
+ ifeq ($(ARCH),ppc64)
+ LIB=lib64
+ else
+ ifeq ($(ARCH),s390x)
+ LIB=lib64
+ endif
+ endif
+ endif
+
+ BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \
+ -pipe -DUSE_ICON
+
+ OPTIMIZEVM = -O3 -funroll-loops -fomit-frame-pointer
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+
+ ifeq ($(ARCH),x86_64)
+ OPTIMIZEVM = -O3 -fomit-frame-pointer -funroll-loops \
+ -falign-functions=2 -fstrength-reduce
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+ HAVE_VM_COMPILED = true
+ else
+ ifeq ($(ARCH),x86)
+ OPTIMIZEVM = -O3 -march=i586 -fomit-frame-pointer \
+ -funroll-loops -falign-functions=2 -fstrength-reduce
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+ HAVE_VM_COMPILED=true
+ else
+ ifeq ($(ARCH),ppc)
+ BASE_CFLAGS += -maltivec
+ HAVE_VM_COMPILED=true
+ endif
+ ifeq ($(ARCH),ppc64)
+ BASE_CFLAGS += -maltivec
+ HAVE_VM_COMPILED=true
+ endif
+ ifeq ($(ARCH),sparc)
+ OPTIMIZE += -mtune=ultrasparc3 -mv8plus
+ OPTIMIZEVM += -mtune=ultrasparc3 -mv8plus
+ HAVE_VM_COMPILED=true
+ endif
+ ifeq ($(ARCH),alpha)
+ # According to http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=410555
+ # -ffast-math will cause the client to die with SIGFPE on Alpha
+ OPTIMIZE = $(OPTIMIZEVM)
+ endif
+ endif
+ endif
+
+ SHLIBEXT=so
+ SHLIBCFLAGS=-fPIC -fvisibility=hidden
+ SHLIBLDFLAGS=-shared $(LDFLAGS)
+
+ THREAD_LIBS=-lpthread
+ LIBS=-ldl -lm
+
+ ifeq ($(ARCH),x86)
+ # linux32 make ...
+ BASE_CFLAGS += -m32
+ else
+ ifeq ($(ARCH),ppc64)
+ BASE_CFLAGS += -m64
+ endif
+ endif
+else # ifeq Linux
+
+#############################################################################
+# SETUP AND BUILD -- MAC OS X
+#############################################################################
+
+ifeq ($(PLATFORM),darwin)
+ HAVE_VM_COMPILED=true
+ LIBS = -framework Cocoa
+ OPTIMIZEVM=
+
+ BASE_CFLAGS = -Wall -Wimplicit -Wstrict-prototypes -mmacosx-version-min=10.5 \
+ -DMAC_OS_X_VERSION_MIN_REQUIRED=1050
+
+ ifeq ($(ARCH),ppc)
+ BASE_CFLAGS += -arch ppc -faltivec
+ OPTIMIZEVM += -O3
+ endif
+ ifeq ($(ARCH),ppc64)
+ BASE_CFLAGS += -arch ppc64 -faltivec
+ endif
+ ifeq ($(ARCH),x86)
+ OPTIMIZEVM += -mfpmath=387+sse
+ # x86 vm will crash without -mstackrealign since MMX instructions will be
+ # used no matter what and they corrupt the frame pointer in VM calls
+ BASE_CFLAGS += -arch i386 -m32 -mstackrealign
+ endif
+ ifeq ($(ARCH),x86_64)
+ OPTIMIZEVM += -arch x86_64 -mfpmath=sse -msse2
+ endif
+
+ # When compiling on OSX for OSX, we're not cross compiling as far as the
+ # Makefile is concerned, as target architecture is specified as a compiler
+ # argument
+ ifeq ($(COMPILE_PLATFORM),darwin)
+ CROSS_COMPILING=0
+ endif
+
+
+ ifeq ($(CROSS_COMPILING),1)
+ ifeq ($(ARCH),x86_64)
+ CC=x86_64-apple-darwin13-cc
+ RANLIB=x86_64-apple-darwin13-ranlib
+ else
+ ifeq ($(ARCH),x86)
+ CC=i386-apple-darwin13-cc
+ RANLIB=i386-apple-darwin13-ranlib
+ else
+ $(error Architecture $(ARCH) is not supported when cross compiling)
+ endif
+ endif
+ else
+ TOOLS_CFLAGS += -DMACOS_X
+ endif
+
+ BASE_CFLAGS += -fno-strict-aliasing -DMACOS_X -fno-common -pipe
+
+ BASE_CFLAGS += -D_THREAD_SAFE=1
+
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+
+ SHLIBEXT=dylib
+ SHLIBCFLAGS=-fPIC -fno-common
+ SHLIBLDFLAGS=-dynamiclib $(LDFLAGS) -Wl,-U,_com_altivec
+
+ NOTSHLIBCFLAGS=-mdynamic-no-pic
+
+else # ifeq darwin
+
+
+#############################################################################
+# SETUP AND BUILD -- MINGW32
+#############################################################################
+
+ifeq ($(PLATFORM),mingw32)
+
+ ifeq ($(CROSS_COMPILING),1)
+ # If CC is already set to something generic, we probably want to use
+ # something more specific
+ ifneq ($(findstring $(strip $(CC)),cc gcc),)
+ CC=
+ endif
+
+ # We need to figure out the correct gcc and windres
+ ifeq ($(ARCH),x86_64)
+ MINGW_PREFIXES=amd64-mingw32msvc x86_64-w64-mingw32
+ endif
+ ifeq ($(ARCH),x86)
+ MINGW_PREFIXES=i586-mingw32msvc i686-w64-mingw32
+ endif
+
+ ifndef CC
+ CC=$(strip $(foreach MINGW_PREFIX, $(MINGW_PREFIXES), \
+ $(call bin_path, $(MINGW_PREFIX)-gcc)))
+ endif
+ else
+ # Some MinGW installations define CC to cc, but don't actually provide cc,
+ # so check that CC points to a real binary and use gcc if it doesn't
+ ifeq ($(call bin_path, $(CC)),)
+ CC=gcc
+ endif
+ endif
+
+ ifeq ($(CC),)
+ $(error Cannot find a suitable cross compiler for $(PLATFORM))
+ endif
+
+ BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \
+ -DUSE_ICON
+
+ # In the absence of wspiapi.h, require Windows XP or later
+ ifeq ($(shell test -e $(CMDIR)/wspiapi.h; echo $$?),1)
+ BASE_CFLAGS += -DWINVER=0x501
+ endif
+
+ ifeq ($(ARCH),x86_64)
+ OPTIMIZEVM = -O3 -fno-omit-frame-pointer \
+ -funroll-loops -falign-functions=2 -fstrength-reduce
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+ HAVE_VM_COMPILED = true
+ endif
+ ifeq ($(ARCH),x86)
+ OPTIMIZEVM = -O3 -march=i586 -fno-omit-frame-pointer \
+ -funroll-loops -falign-functions=2 -fstrength-reduce
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+ HAVE_VM_COMPILED = true
+ endif
+
+ SHLIBEXT=dll
+ SHLIBCFLAGS=
+ SHLIBLDFLAGS=-shared $(LDFLAGS)
+
+ ifeq ($(CROSS_COMPILING),0)
+ TOOLS_BINEXT=.exe
+ endif
+
+ ifeq ($(COMPILE_PLATFORM),cygwin)
+ TOOLS_BINEXT=.exe
+ TOOLS_CC=$(CC)
+ endif
+
+ LIBS= -lws2_32 -lwinmm -lpsapi
+
+ ifeq ($(ARCH),x86)
+ # build 32bit
+ BASE_CFLAGS += -m32
+ else
+ BASE_CFLAGS += -m64
+ endif
+
+else # ifeq mingw32
+
+#############################################################################
+# SETUP AND BUILD -- FREEBSD
+#############################################################################
+
+ifeq ($(PLATFORM),freebsd)
+
+ # flags
+ BASE_CFLAGS = -Wall -fno-strict-aliasing -DUSE_ICON
+ HAVE_VM_COMPILED = true
+
+ OPTIMIZEVM = -O3 -funroll-loops -fomit-frame-pointer
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+
+ SHLIBEXT=so
+ SHLIBCFLAGS=-fPIC
+ SHLIBLDFLAGS=-shared $(LDFLAGS)
+
+ # don't need -ldl (FreeBSD)
+ LIBS=-lm
+
+ # cross-compiling tweaks
+ ifeq ($(ARCH),x86)
+ ifeq ($(CROSS_COMPILING),1)
+ BASE_CFLAGS += -m32
+ endif
+ endif
+ ifeq ($(ARCH),x86_64)
+ ifeq ($(CROSS_COMPILING),1)
+ BASE_CFLAGS += -m64
+ endif
+ endif
+else # ifeq freebsd
+
+#############################################################################
+# SETUP AND BUILD -- OPENBSD
+#############################################################################
+
+ifeq ($(PLATFORM),openbsd)
+
+ BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \
+ -pipe -DUSE_ICON -DMAP_ANONYMOUS=MAP_ANON
+
+ OPTIMIZEVM = -O3 -funroll-loops -fomit-frame-pointer
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+
+ ifeq ($(ARCH),x86_64)
+ OPTIMIZEVM = -O3 -fomit-frame-pointer -funroll-loops \
+ -falign-functions=2 -fstrength-reduce
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+ HAVE_VM_COMPILED = true
+ else
+ ifeq ($(ARCH),x86)
+ OPTIMIZEVM = -O3 -march=i586 -fomit-frame-pointer \
+ -funroll-loops -falign-functions=2 -fstrength-reduce
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+ HAVE_VM_COMPILED=true
+ else
+ ifeq ($(ARCH),ppc)
+ BASE_CFLAGS += -maltivec
+ HAVE_VM_COMPILED=true
+ endif
+ ifeq ($(ARCH),ppc64)
+ BASE_CFLAGS += -maltivec
+ HAVE_VM_COMPILED=true
+ endif
+ ifeq ($(ARCH),sparc64)
+ OPTIMIZE += -mtune=ultrasparc3 -mv8plus
+ OPTIMIZEVM += -mtune=ultrasparc3 -mv8plus
+ HAVE_VM_COMPILED=true
+ endif
+ ifeq ($(ARCH),alpha)
+ # According to http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=410555
+ # -ffast-math will cause the client to die with SIGFPE on Alpha
+ OPTIMIZE = $(OPTIMIZEVM)
+ endif
+ endif
+ endif
+
+ SHLIBEXT=so
+ SHLIBCFLAGS=-fPIC
+ SHLIBLDFLAGS=-shared $(LDFLAGS)
+
+ THREAD_LIBS=-lpthread
+ LIBS=-lm
+else # ifeq openbsd
+
+#############################################################################
+# SETUP AND BUILD -- NETBSD
+#############################################################################
+
+ifeq ($(PLATFORM),netbsd)
+
+ LIBS=-lm
+ SHLIBEXT=so
+ SHLIBCFLAGS=-fPIC
+ SHLIBLDFLAGS=-shared $(LDFLAGS)
+ THREAD_LIBS=-lpthread
+
+ BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes
+
+ ifeq ($(ARCH),x86)
+ HAVE_VM_COMPILED=true
+ endif
+else # ifeq netbsd
+
+#############################################################################
+# SETUP AND BUILD -- IRIX
+#############################################################################
+
+ifeq ($(PLATFORM),irix64)
+
+ ARCH=mips
+
+ CC = c99
+ MKDIR = mkdir -p
+
+ BASE_CFLAGS=-Dstricmp=strcasecmp -Xcpluscomm -woff 1185 \
+ -I. -I$(ROOT)/usr/include
+ OPTIMIZE = -O3
+
+ SHLIBEXT=so
+ SHLIBCFLAGS=
+ SHLIBLDFLAGS=-shared
+
+ LIBS=-ldl -lm -lgen
+else # ifeq IRIX
+
+#############################################################################
+# SETUP AND BUILD -- SunOS
+#############################################################################
+
+ifeq ($(PLATFORM),sunos)
+
+ CC=gcc
+ INSTALL=ginstall
+ MKDIR=gmkdir
+ COPYDIR="/usr/local/share/games/tremulous"
+
+ ifneq ($(ARCH),x86)
+ ifneq ($(ARCH),sparc)
+ $(error arch $(ARCH) is currently not supported)
+ endif
+ endif
+
+ BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \
+ -pipe -DUSE_ICON
+
+ OPTIMIZEVM = -O3 -funroll-loops
+
+ ifeq ($(ARCH),sparc)
+ OPTIMIZEVM += -O3 \
+ -fstrength-reduce -falign-functions=2 \
+ -mtune=ultrasparc3 -mv8plus -mno-faster-structs
+ HAVE_VM_COMPILED=true
+ else
+ ifeq ($(ARCH),x86)
+ OPTIMIZEVM += -march=i586 -fomit-frame-pointer \
+ -falign-functions=2 -fstrength-reduce
+ HAVE_VM_COMPILED=true
+ BASE_CFLAGS += -m32
+ endif
+ endif
+
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+
+ SHLIBEXT=so
+ SHLIBCFLAGS=-fPIC
+ SHLIBLDFLAGS=-shared $(LDFLAGS)
+
+ THREAD_LIBS=-lpthread
+ LIBS=-lsocket -lnsl -ldl -lm
+else # ifeq sunos
+
+#############################################################################
+# SETUP AND BUILD -- GENERIC
+#############################################################################
+ BASE_CFLAGS=
+ OPTIMIZE = -O3
+
+ SHLIBEXT=so
+ SHLIBCFLAGS=-fPIC
+ SHLIBLDFLAGS=-shared
+
+endif #Linux
+endif #darwin
+endif #mingw32
+endif #FreeBSD
+endif #OpenBSD
+endif #NetBSD
+endif #IRIX
+endif #SunOS
+
+ifndef CC
+ CC=gcc
+endif
+
+ifndef RANLIB
+ RANLIB=ranlib
+endif
+
+ifneq ($(HAVE_VM_COMPILED),true)
+ BASE_CFLAGS += -DNO_VM_COMPILED
+ BUILD_GAME_QVM=0
+endif
+
+TARGETS =
+
+ifndef FULLBINEXT
+ FULLBINEXT=$(BINEXT)
+endif
+
+ifndef SHLIBNAME
+ SHLIBNAME=.$(SHLIBEXT)
+endif
+
+ifneq ($(BUILD_GAME_SO),0)
+ ifeq ($(BUILD_ONLY_GAME),1)
+ TARGETS += \
+ $(B)/out/$(BASEGAME)/game$(SHLIBNAME)
+ else
+ ifeq ($(BUILD_ONLY_CGUI),1)
+ TARGETS += \
+ $(B)/out/$(BASEGAME)/cgame$(SHLIBNAME) \
+ $(B)/out/$(BASEGAME)/ui$(SHLIBNAME)
+ else
+ TARGETS += \
+ $(B)/out/$(BASEGAME)/cgame$(SHLIBNAME) \
+ $(B)/out/$(BASEGAME)/game$(SHLIBNAME) \
+ $(B)/out/$(BASEGAME)/ui$(SHLIBNAME)
+ endif
+ endif
+endif
+
+ifneq ($(BUILD_GAME_QVM),0)
+ ifeq ($(BUILD_ONLY_GAME),1)
+ TARGETS += \
+ $(B)/out/$(BASEGAME)/vm/game.qvm
+ else
+ ifeq ($(BUILD_ONLY_CGUI),1)
+ TARGETS += \
+ $(B)/out/$(BASEGAME)/vm/cgame.qvm \
+ $(B)/out/$(BASEGAME)/vm/ui.qvm \
+ $(B)/out/$(BASEGAME)/vms-gpp1-$(VERSION).pk3
+ else
+ TARGETS += \
+ $(B)/out/$(BASEGAME)/vm/cgame.qvm \
+ $(B)/out/$(BASEGAME)/vm/game.qvm \
+ $(B)/out/$(BASEGAME)/vm/ui.qvm \
+ $(B)/out/$(BASEGAME)/vms-gpp1-$(VERSION).pk3
+ endif
+ endif
+endif
+
+ifneq ($(BUILD_GAME_QVM_11),0)
+ ifneq ($(BUILD_ONLY_GAME),1)
+ TARGETS += \
+ $(B)/out/$(BASEGAME)_11/vm/cgame.qvm \
+ $(B)/out/$(BASEGAME)_11/vm/ui.qvm \
+ $(B)/out/$(BASEGAME)_11/vms-1.1.0-$(VERSION).pk3
+ endif
+endif
+
+ifeq ("$(CC)", $(findstring "$(CC)", "clang" "clang++"))
+ BASE_CFLAGS += -Qunused-arguments
+endif
+
+ifdef DEFAULT_BASEDIR
+ BASE_CFLAGS += -DDEFAULT_BASEDIR=\\\"$(DEFAULT_BASEDIR)\\\"
+endif
+
+ifeq ($(GENERATE_DEPENDENCIES),1)
+ DEPEND_CFLAGS = -MMD
+else
+ DEPEND_CFLAGS =
+endif
+
+ifeq ($(NO_STRIP),1)
+ STRIP_FLAG =
+else
+ STRIP_FLAG = -s
+endif
+
+BASE_CFLAGS += -DPRODUCT_VERSION=\\\"$(VERSION)\\\"
+
+ifeq ($(V),1)
+echo_cmd=@:
+Q=
+else
+echo_cmd=@echo
+Q=@
+endif
+
+ifeq ($(GENERATE_DEPENDENCIES),1)
+ DO_QVM_DEP=cat $(@:%.o=%.d) | sed -e 's/\.o/\.asm/g' >> $(@:%.o=%.d)
+endif
+
+define DO_SHLIB_CC
+$(echo_cmd) "SHLIB_CC $<"
+$(Q)$(CC) $(BASEGAME_CFLAGS) $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $<
+$(Q)$(DO_QVM_DEP)
+endef
+
+define DO_GAME_CC
+$(echo_cmd) "GAME_CC $<"
+$(Q)$(CC) $(BASEGAME_CFLAGS) -DGAME $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $<
+$(Q)$(DO_QVM_DEP)
+endef
+
+define DO_CGAME_CC
+$(echo_cmd) "CGAME_CC $<"
+$(Q)$(CC) $(BASEGAME_CFLAGS) -DCGAME $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $<
+$(Q)$(DO_QVM_DEP)
+endef
+
+define DO_UI_CC
+$(echo_cmd) "UI_CC $<"
+$(Q)$(CC) $(BASEGAME_CFLAGS) -DUI $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $<
+$(Q)$(DO_QVM_DEP)
+endef
+
+define DO_AS
+$(echo_cmd) "AS $<"
+$(Q)$(CC) $(CFLAGS) $(OPTIMIZE) -x assembler-with-cpp -o $@ -c $<
+endef
+
+
+#############################################################################
+# MAIN TARGETS
+#############################################################################
+
+default: release
+all: debug release
+
+debug:
+ @$(MAKE) targets B=$(BD) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \
+ OPTIMIZE="$(DEBUG_CFLAGS)" OPTIMIZEVM="$(DEBUG_CFLAGS)" V=$(V)
+
+release:
+ @$(MAKE) targets B=$(BR) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \
+ OPTIMIZE="-DNDEBUG $(OPTIMIZE)" OPTIMIZEVM="-DNDEBUG $(OPTIMIZEVM)" V=$(V)
+
+ifneq ($(call bin_path, tput),)
+ TERM_COLUMNS=$(shell echo $$((`tput cols`-4)))
+else
+ TERM_COLUMNS=76
+endif
+
+NAKED_TARGETS=$(shell echo $(TARGETS) | sed -e "s!$(B)/!!g")
+
+print_list=@for i in $(1); \
+ do \
+ echo " $$i"; \
+ done
+
+ifneq ($(call bin_path, fmt),)
+ print_wrapped=@echo $(1) | fmt -w $(TERM_COLUMNS) | sed -e "s/^\(.*\)$$/ \1/"
+else
+ print_wrapped=$(print_list)
+endif
+
+# Create the build directories, check libraries and print out
+# an informational message, then start building
+targets: makedirs
+ @echo ""
+ @echo "Building in $(B):"
+ @echo " PLATFORM: $(PLATFORM)"
+ @echo " ARCH: $(ARCH)"
+ @echo " VERSION: $(VERSION)"
+ @echo " COMPILE_PLATFORM: $(COMPILE_PLATFORM)"
+ @echo " COMPILE_ARCH: $(COMPILE_ARCH)"
+ @echo " CC: $(CC)"
+ @echo ""
+ @echo " CFLAGS:"
+ $(call print_wrapped, $(CFLAGS) $(OPTIMIZE))
+ @echo ""
+ @echo " LDFLAGS:"
+ $(call print_wrapped, $(LDFLAGS))
+ @echo ""
+ @echo " LIBS:"
+ $(call print_wrapped, $(LIBS))
+ @echo ""
+ @echo " Output:"
+ $(call print_list, $(NAKED_TARGETS))
+ @echo ""
+ifneq ($(TARGETS),)
+ ifndef DEBUG_MAKEFILE
+ @$(MAKE) $(TARGETS) V=$(V)
+ endif
+endif
+
+makedirs:
+ @if [ ! -d $(BUILD_DIR) ];then $(MKDIR) $(BUILD_DIR);fi
+ @if [ ! -d $(B) ];then $(MKDIR) $(B);fi
+ @if [ ! -d $(B)/cgame ];then $(MKDIR) $(B)/cgame;fi
+ @if [ ! -d $(B)/game ];then $(MKDIR) $(B)/game;fi
+ @if [ ! -d $(B)/ui ];then $(MKDIR) $(B)/ui;fi
+ @if [ ! -d $(B)/qcommon ];then $(MKDIR) $(B)/qcommon;fi
+ @if [ ! -d $(B)/11 ];then $(MKDIR) $(B)/11;fi
+ @if [ ! -d $(B)/11/cgame ];then $(MKDIR) $(B)/11/cgame;fi
+ @if [ ! -d $(B)/11/ui ];then $(MKDIR) $(B)/11/ui;fi
+ @if [ ! -d $(B)/out ];then $(MKDIR) $(B)/out;fi
+ @if [ ! -d $(B)/out/$(BASEGAME) ];then $(MKDIR) $(B)/out/$(BASEGAME);fi
+ @if [ ! -d $(B)/out/$(BASEGAME)/vm ];then $(MKDIR) $(B)/out/$(BASEGAME)/vm;fi
+ @if [ ! -d $(B)/out/$(BASEGAME)_11 ];then $(MKDIR) $(B)/out/$(BASEGAME)_11;fi
+ @if [ ! -d $(B)/out/$(BASEGAME)_11/vm ];then $(MKDIR) $(B)/out/$(BASEGAME)_11/vm;fi
+ @if [ ! -d $(B)/tools ];then $(MKDIR) $(B)/tools;fi
+ @if [ ! -d $(B)/tools/asm ];then $(MKDIR) $(B)/tools/asm;fi
+ @if [ ! -d $(B)/tools/etc ];then $(MKDIR) $(B)/tools/etc;fi
+ @if [ ! -d $(B)/tools/rcc ];then $(MKDIR) $(B)/tools/rcc;fi
+ @if [ ! -d $(B)/tools/cpp ];then $(MKDIR) $(B)/tools/cpp;fi
+ @if [ ! -d $(B)/tools/lburg ];then $(MKDIR) $(B)/tools/lburg;fi
+
+#############################################################################
+# QVM BUILD TOOLS
+#############################################################################
+
+ifndef TOOLS_CC
+ # A compiler which probably produces native binaries
+ TOOLS_CC=$(CC)
+endif
+
+TOOLS_OPTIMIZE = -g -Wall -fno-strict-aliasing
+TOOLS_CFLAGS += $(TOOLS_OPTIMIZE) \
+ -DTEMPDIR=\"$(TEMPDIR)\" -DSYSTEM=\"\" \
+ -I$(Q3LCCSRCDIR) \
+ -I$(LBURGDIR)
+TOOLS_LIBS =
+TOOLS_LDFLAGS =
+
+ifeq ($(GENERATE_DEPENDENCIES),1)
+ TOOLS_CFLAGS += -MMD
+endif
+
+define DO_TOOLS_CC
+$(echo_cmd) "TOOLS_CC $<"
+$(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) -o $@ -c $<
+endef
+
+define DO_TOOLS_CC_DAGCHECK
+$(echo_cmd) "TOOLS_CC_DAGCHECK $<"
+$(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) -Wno-unused -o $@ -c $<
+endef
+
+LBURG = $(B)/tools/lburg/lburg$(TOOLS_BINEXT)
+DAGCHECK_C = $(B)/tools/rcc/dagcheck.c
+Q3RCC = $(B)/tools/q3rcc$(TOOLS_BINEXT)
+Q3CPP = $(B)/tools/q3cpp$(TOOLS_BINEXT)
+Q3LCC = $(B)/tools/q3lcc$(TOOLS_BINEXT)
+Q3ASM = $(B)/tools/q3asm$(TOOLS_BINEXT)
+
+LBURGOBJ= \
+ $(B)/tools/lburg/lburg.o \
+ $(B)/tools/lburg/gram.o
+
+$(B)/tools/lburg/%.o: $(LBURGDIR)/%.c
+ $(DO_TOOLS_CC)
+
+$(LBURG): $(LBURGOBJ)
+ $(echo_cmd) "LD $@"
+ $(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS)
+
+Q3RCCOBJ = \
+ $(B)/tools/rcc/alloc.o \
+ $(B)/tools/rcc/bind.o \
+ $(B)/tools/rcc/bytecode.o \
+ $(B)/tools/rcc/dag.o \
+ $(B)/tools/rcc/dagcheck.o \
+ $(B)/tools/rcc/decl.o \
+ $(B)/tools/rcc/enode.o \
+ $(B)/tools/rcc/error.o \
+ $(B)/tools/rcc/event.o \
+ $(B)/tools/rcc/expr.o \
+ $(B)/tools/rcc/gen.o \
+ $(B)/tools/rcc/init.o \
+ $(B)/tools/rcc/inits.o \
+ $(B)/tools/rcc/input.o \
+ $(B)/tools/rcc/lex.o \
+ $(B)/tools/rcc/list.o \
+ $(B)/tools/rcc/main.o \
+ $(B)/tools/rcc/null.o \
+ $(B)/tools/rcc/output.o \
+ $(B)/tools/rcc/prof.o \
+ $(B)/tools/rcc/profio.o \
+ $(B)/tools/rcc/simp.o \
+ $(B)/tools/rcc/stmt.o \
+ $(B)/tools/rcc/string.o \
+ $(B)/tools/rcc/sym.o \
+ $(B)/tools/rcc/symbolic.o \
+ $(B)/tools/rcc/trace.o \
+ $(B)/tools/rcc/tree.o \
+ $(B)/tools/rcc/types.o
+
+$(DAGCHECK_C): $(LBURG) $(Q3LCCSRCDIR)/dagcheck.md
+ $(echo_cmd) "LBURG $(Q3LCCSRCDIR)/dagcheck.md"
+ $(Q)$(LBURG) $(Q3LCCSRCDIR)/dagcheck.md $@
+
+$(B)/tools/rcc/dagcheck.o: $(DAGCHECK_C)
+ $(DO_TOOLS_CC_DAGCHECK)
+
+$(B)/tools/rcc/%.o: $(Q3LCCSRCDIR)/%.c
+ $(DO_TOOLS_CC)
+
+$(Q3RCC): $(Q3RCCOBJ)
+ $(echo_cmd) "LD $@"
+ $(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS)
+
+Q3CPPOBJ = \
+ $(B)/tools/cpp/cpp.o \
+ $(B)/tools/cpp/lex.o \
+ $(B)/tools/cpp/nlist.o \
+ $(B)/tools/cpp/tokens.o \
+ $(B)/tools/cpp/macro.o \
+ $(B)/tools/cpp/eval.o \
+ $(B)/tools/cpp/include.o \
+ $(B)/tools/cpp/hideset.o \
+ $(B)/tools/cpp/getopt.o \
+ $(B)/tools/cpp/unix.o
+
+$(B)/tools/cpp/%.o: $(Q3CPPDIR)/%.c
+ $(DO_TOOLS_CC)
+
+$(Q3CPP): $(Q3CPPOBJ)
+ $(echo_cmd) "LD $@"
+ $(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS)
+
+Q3LCCOBJ = \
+ $(B)/tools/etc/lcc.o \
+ $(B)/tools/etc/bytecode.o
+
+$(B)/tools/etc/%.o: $(Q3LCCETCDIR)/%.c
+ $(DO_TOOLS_CC)
+
+$(Q3LCC): $(Q3LCCOBJ) $(Q3RCC) $(Q3CPP)
+ $(echo_cmd) "LD $@"
+ $(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $(Q3LCCOBJ) $(TOOLS_LIBS)
+
+define DO_Q3LCC
+$(echo_cmd) "Q3LCC $<"
+$(Q)$(Q3LCC) $(BASEGAME_CFLAGS) -o $@ $<
+endef
+
+define DO_CGAME_Q3LCC
+$(echo_cmd) "CGAME_Q3LCC $<"
+$(Q)$(Q3LCC) $(BASEGAME_CFLAGS) -DCGAME -o $@ $<
+endef
+
+define DO_CGAME_Q3LCC_11
+$(echo_cmd) "CGAME_Q3LCC_11 $<"
+$(Q)$(Q3LCC) $(BASEGAME_CFLAGS) -DCGAME -DMODULE_INTERFACE_11 -o $@ $<
+endef
+
+define DO_GAME_Q3LCC
+$(echo_cmd) "GAME_Q3LCC $<"
+$(Q)$(Q3LCC) $(BASEGAME_CFLAGS) -DGAME -o $@ $<
+endef
+
+define DO_UI_Q3LCC
+$(echo_cmd) "UI_Q3LCC $<"
+$(Q)$(Q3LCC) $(BASEGAME_CFLAGS) -DUI -o $@ $<
+endef
+
+define DO_UI_Q3LCC_11
+$(echo_cmd) "UI_Q3LCC_11 $<"
+$(Q)$(Q3LCC) $(BASEGAME_CFLAGS) -DUI -DMODULE_INTERFACE_11 -o $@ $<
+endef
+
+
+Q3ASMOBJ = \
+ $(B)/tools/asm/q3asm.o \
+ $(B)/tools/asm/cmdlib.o
+
+$(B)/tools/asm/%.o: $(Q3ASMDIR)/%.c
+ $(DO_TOOLS_CC)
+
+$(Q3ASM): $(Q3ASMOBJ)
+ $(echo_cmd) "LD $@"
+ $(Q)$(TOOLS_CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS)
+
+
+#############################################################################
+## TREMULOUS CGAME
+#############################################################################
+
+CGOBJ_ = \
+ $(B)/cgame/cg_main.o \
+ $(B)/cgame/bg_misc.o \
+ $(B)/cgame/bg_pmove.o \
+ $(B)/cgame/bg_slidemove.o \
+ $(B)/cgame/cg_consolecmds.o \
+ $(B)/cgame/cg_buildable.o \
+ $(B)/cgame/cg_animation.o \
+ $(B)/cgame/cg_animmapobj.o \
+ $(B)/cgame/cg_draw.o \
+ $(B)/cgame/cg_drawtools.o \
+ $(B)/cgame/cg_ents.o \
+ $(B)/cgame/cg_event.o \
+ $(B)/cgame/cg_marks.o \
+ $(B)/cgame/cg_players.o \
+ $(B)/cgame/cg_playerstate.o \
+ $(B)/cgame/cg_predict.o \
+ $(B)/cgame/cg_servercmds.o \
+ $(B)/cgame/cg_snapshot.o \
+ $(B)/cgame/cg_view.o \
+ $(B)/cgame/cg_weapons.o \
+ $(B)/cgame/cg_mem.o \
+ $(B)/cgame/cg_scanner.o \
+ $(B)/cgame/cg_attachment.o \
+ $(B)/cgame/cg_trails.o \
+ $(B)/cgame/cg_particles.o \
+ $(B)/cgame/cg_ptr.o \
+ $(B)/cgame/cg_tutorial.o \
+ $(B)/cgame/ui_shared.o \
+ \
+ $(B)/qcommon/q_math.o \
+ $(B)/qcommon/q_shared.o
+
+CGOBJ11_ = \
+ $(B)/11/cgame/cg_main.o \
+ $(B)/cgame/bg_misc.o \
+ $(B)/cgame/bg_pmove.o \
+ $(B)/cgame/bg_slidemove.o \
+ $(B)/cgame/cg_consolecmds.o \
+ $(B)/cgame/cg_buildable.o \
+ $(B)/cgame/cg_animation.o \
+ $(B)/cgame/cg_animmapobj.o \
+ $(B)/cgame/cg_draw.o \
+ $(B)/cgame/cg_drawtools.o \
+ $(B)/cgame/cg_ents.o \
+ $(B)/cgame/cg_event.o \
+ $(B)/cgame/cg_marks.o \
+ $(B)/cgame/cg_players.o \
+ $(B)/cgame/cg_playerstate.o \
+ $(B)/cgame/cg_predict.o \
+ $(B)/11/cgame/cg_servercmds.o \
+ $(B)/11/cgame/cg_snapshot.o \
+ $(B)/cgame/cg_view.o \
+ $(B)/cgame/cg_weapons.o \
+ $(B)/cgame/cg_mem.o \
+ $(B)/cgame/cg_scanner.o \
+ $(B)/cgame/cg_attachment.o \
+ $(B)/cgame/cg_trails.o \
+ $(B)/cgame/cg_particles.o \
+ $(B)/cgame/cg_ptr.o \
+ $(B)/cgame/cg_tutorial.o \
+ $(B)/cgame/ui_shared.o \
+ \
+ $(B)/qcommon/q_math.o \
+ $(B)/qcommon/q_shared.o
+
+CGOBJ = $(CGOBJ_) $(B)/cgame/cg_syscalls.o
+CGVMOBJ = $(CGOBJ_:%.o=%.asm) $(B)/cgame/bg_lib.asm
+CGVMOBJ11 = $(CGOBJ11_:%.o=%.asm) $(B)/cgame/bg_lib.asm
+
+$(B)/out/$(BASEGAME)/cgame$(SHLIBNAME): $(CGOBJ)
+ $(echo_cmd) "LD $@"
+ $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(CGOBJ)
+
+$(B)/out/$(BASEGAME)/vm/cgame.qvm: $(CGVMOBJ) $(CGDIR)/cg_syscalls.asm $(Q3ASM)
+ $(echo_cmd) "Q3ASM $@"
+ $(Q)$(Q3ASM) -o $@ $(CGVMOBJ) $(CGDIR)/cg_syscalls.asm
+
+$(B)/out/$(BASEGAME)_11/vm/cgame.qvm: $(CGVMOBJ11) $(CGDIR)/cg_syscalls_11.asm $(Q3ASM)
+ $(echo_cmd) "Q3ASM $@"
+ $(Q)$(Q3ASM) -o $@ $(CGVMOBJ11) $(CGDIR)/cg_syscalls_11.asm
+
+
+
+#############################################################################
+## TREMULOUS GAME
+#############################################################################
+
+GOBJ_ = \
+ $(B)/game/g_main.o \
+ $(B)/game/bg_misc.o \
+ $(B)/game/bg_pmove.o \
+ $(B)/game/bg_slidemove.o \
+ $(B)/game/g_mem.o \
+ $(B)/game/g_active.o \
+ $(B)/game/g_client.o \
+ $(B)/game/g_cmds.o \
+ $(B)/game/g_combat.o \
+ $(B)/game/g_physics.o \
+ $(B)/game/g_buildable.o \
+ $(B)/game/g_misc.o \
+ $(B)/game/g_missile.o \
+ $(B)/game/g_mover.o \
+ $(B)/game/g_session.o \
+ $(B)/game/g_spawn.o \
+ $(B)/game/g_svcmds.o \
+ $(B)/game/g_target.o \
+ $(B)/game/g_team.o \
+ $(B)/game/g_trigger.o \
+ $(B)/game/g_utils.o \
+ $(B)/game/g_maprotation.o \
+ $(B)/game/g_ptr.o \
+ $(B)/game/g_weapon.o \
+ $(B)/game/g_admin.o \
+ \
+ $(B)/qcommon/q_math.o \
+ $(B)/qcommon/q_shared.o
+
+GOBJ = $(GOBJ_) $(B)/game/g_syscalls.o
+GVMOBJ = $(GOBJ_:%.o=%.asm) $(B)/game/bg_lib.asm
+
+$(B)/out/$(BASEGAME)/game$(SHLIBNAME): $(GOBJ)
+ $(echo_cmd) "LD $@"
+ $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GOBJ)
+
+$(B)/out/$(BASEGAME)/vm/game.qvm: $(GVMOBJ) $(GDIR)/g_syscalls.asm $(Q3ASM)
+ $(echo_cmd) "Q3ASM $@"
+ $(Q)$(Q3ASM) -o $@ $(GVMOBJ) $(GDIR)/g_syscalls.asm
+
+
+
+#############################################################################
+## TREMULOUS UI
+#############################################################################
+
+UIOBJ_ = \
+ $(B)/ui/ui_main.o \
+ $(B)/ui/ui_atoms.o \
+ $(B)/ui/ui_players.o \
+ $(B)/ui/ui_shared.o \
+ $(B)/ui/ui_gameinfo.o \
+ \
+ $(B)/ui/bg_misc.o \
+ $(B)/qcommon/q_math.o \
+ $(B)/qcommon/q_shared.o
+
+UIOBJ11_ = \
+ $(B)/11/ui/ui_main.o \
+ $(B)/ui/ui_atoms.o \
+ $(B)/ui/ui_players.o \
+ $(B)/ui/ui_shared.o \
+ $(B)/ui/ui_gameinfo.o \
+ \
+ $(B)/ui/bg_misc.o \
+ $(B)/qcommon/q_math.o \
+ $(B)/qcommon/q_shared.o
+
+UIOBJ = $(UIOBJ_) $(B)/ui/ui_syscalls.o
+UIVMOBJ = $(UIOBJ_:%.o=%.asm) $(B)/ui/bg_lib.asm
+UIVMOBJ11 = $(UIOBJ11_:%.o=%.asm) $(B)/ui/bg_lib.asm
+
+$(B)/out/$(BASEGAME)/ui$(SHLIBNAME): $(UIOBJ)
+ $(echo_cmd) "LD $@"
+ $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(UIOBJ)
+
+$(B)/out/$(BASEGAME)/vm/ui.qvm: $(UIVMOBJ) $(UIDIR)/ui_syscalls.asm $(Q3ASM)
+ $(echo_cmd) "Q3ASM $@"
+ $(Q)$(Q3ASM) -o $@ $(UIVMOBJ) $(UIDIR)/ui_syscalls.asm
+
+$(B)/out/$(BASEGAME)_11/vm/ui.qvm: $(UIVMOBJ11) $(UIDIR)/ui_syscalls_11.asm $(Q3ASM)
+ $(echo_cmd) "Q3ASM $@"
+ $(Q)$(Q3ASM) -o $@ $(UIVMOBJ11) $(UIDIR)/ui_syscalls_11.asm
+
+
+#############################################################################
+## QVM Package
+#############################################################################
+
+$(B)/out/$(BASEGAME)/vms-gpp1-$(VERSION).pk3: $(B)/out/$(BASEGAME)/vm/ui.qvm $(B)/out/$(BASEGAME)/vm/cgame.qvm $(B)/out/$(BASEGAME)/vm/game.qvm
+ @(cd $(B)/out/$(BASEGAME) && zip -r vms-gpp1-$(VERSION).pk3 vm/)
+
+$(B)/out/$(BASEGAME)_11/vms-1.1.0-$(VERSION).pk3: $(B)/out/$(BASEGAME)_11/vm/ui.qvm $(B)/out/$(BASEGAME)_11/vm/cgame.qvm
+ @(cd $(B)/out/$(BASEGAME)_11 && zip -r vms-1.1.0-$(VERSION).pk3 vm/)
+
+#############################################################################
+## Assets Package
+#############################################################################
+
+$(B)/out/$(BASEGAME)/data-$(VERSION).pk3: $(ASSETS_DIR)/ui/main.menu
+ @(cd $(ASSETS_DIR) && zip -r data-$(VERSION).pk3 *)
+ @mv $(ASSETS_DIR)/data-$(VERSION).pk3 $(B)/out/$(BASEGAME)
+
+#############################################################################
+## GAME MODULE RULES
+#############################################################################
+
+$(B)/cgame/bg_%.o: $(GDIR)/bg_%.c
+ $(DO_CGAME_CC)
+
+$(B)/cgame/ui_%.o: $(UIDIR)/ui_%.c
+ $(DO_CGAME_CC)
+
+$(B)/cgame/%.o: $(CGDIR)/%.c
+ $(DO_CGAME_CC)
+
+$(B)/cgame/bg_%.asm: $(GDIR)/bg_%.c $(Q3LCC)
+ $(DO_CGAME_Q3LCC)
+
+$(B)/cgame/ui_%.asm: $(UIDIR)/ui_%.c $(Q3LCC)
+ $(DO_CGAME_Q3LCC)
+
+$(B)/cgame/%.asm: $(CGDIR)/%.c $(Q3LCC)
+ $(DO_CGAME_Q3LCC)
+
+$(B)/11/cgame/%.asm: $(CGDIR)/%.c $(Q3LCC)
+ $(DO_CGAME_Q3LCC_11)
+
+
+$(B)/game/%.o: $(GDIR)/%.c
+ $(DO_GAME_CC)
+
+$(B)/game/%.asm: $(GDIR)/%.c $(Q3LCC)
+ $(DO_GAME_Q3LCC)
+
+
+$(B)/ui/bg_%.o: $(GDIR)/bg_%.c
+ $(DO_UI_CC)
+
+$(B)/ui/%.o: $(UIDIR)/%.c
+ $(DO_UI_CC)
+
+$(B)/ui/bg_%.asm: $(GDIR)/bg_%.c $(Q3LCC)
+ $(DO_UI_Q3LCC)
+
+$(B)/ui/%.asm: $(UIDIR)/%.c $(Q3LCC)
+ $(DO_UI_Q3LCC)
+
+$(B)/11/ui/%.asm: $(UIDIR)/%.c $(Q3LCC)
+ $(DO_UI_Q3LCC_11)
+
+
+$(B)/qcommon/%.o: $(CMDIR)/%.c
+ $(DO_SHLIB_CC)
+
+$(B)/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC)
+ $(DO_Q3LCC)
+
+
+#############################################################################
+# MISC
+#############################################################################
+
+OBJ = $(GOBJ) $(CGOBJ) $(UIOBJ) $(CGOBJ11) $(UIOBJ11) \
+ $(GVMOBJ) $(CGVMOBJ) $(UIVMOBJ) $(CGVMOBJ11) $(UIVMOBJ11)
+TOOLSOBJ = $(LBURGOBJ) $(Q3CPPOBJ) $(Q3RCCOBJ) $(Q3LCCOBJ) $(Q3ASMOBJ)
+STRINGOBJ = $(Q3R2STRINGOBJ)
+
+clean: clean-debug clean-release
+ @$(MAKE) -C $(MASTERDIR) clean
+
+clean-debug:
+ @$(MAKE) clean2 B=$(BD)
+
+clean-release:
+ @$(MAKE) clean2 B=$(BR)
+
+clean2:
+ @echo "CLEAN $(B)"
+ @rm -f $(OBJ)
+ @rm -f $(OBJ_D_FILES)
+ @rm -f $(STRINGOBJ)
+ @rm -f $(TARGETS)
+
+toolsclean: toolsclean-debug toolsclean-release
+
+toolsclean-debug:
+ @$(MAKE) toolsclean2 B=$(BD)
+
+toolsclean-release:
+ @$(MAKE) toolsclean2 B=$(BR)
+
+toolsclean2:
+ @echo "TOOLS_CLEAN $(B)"
+ @rm -f $(TOOLSOBJ)
+ @rm -f $(TOOLSOBJ_D_FILES)
+ @rm -f $(LBURG) $(DAGCHECK_C) $(Q3RCC) $(Q3CPP) $(Q3LCC) $(Q3ASM)
+
+distclean: clean toolsclean
+ @rm -rf $(BUILD_DIR)
+
+dist:
+ git archive --format zip --output $(CLIENTBIN)-$(VERSION).zip HEAD
+
+#############################################################################
+# DEPENDENCIES
+#############################################################################
+
+ifneq ($(B),)
+ OBJ_D_FILES=$(filter %.d,$(OBJ:%.o=%.d))
+ TOOLSOBJ_D_FILES=$(filter %.d,$(TOOLSOBJ:%.o=%.d))
+ -include $(OBJ_D_FILES) $(TOOLSOBJ_D_FILES)
+endif
+
+.PHONY: all clean clean2 clean-debug clean-release copyfiles \
+ debug default dist distclean makedirs \
+ release targets \
+ toolsclean toolsclean2 toolsclean-debug toolsclean-release \
+ $(OBJ_D_FILES) $(TOOLSOBJ_D_FILES)
+
+# If the target name contains "clean", don't do a parallel build
+ifneq ($(findstring clean, $(MAKECMDGOALS)),)
+.NOTPARALLEL:
+endif
diff --git a/GPL b/GPL
new file mode 100644
index 0000000..050f1e6
--- /dev/null
+++ b/GPL
@@ -0,0 +1,281 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cc274b0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+Multiprotocol-AA-QVM
+==========================
+
+This file is packaged with source, binaries, and/or assets for the
+multiprotocol AA QVM.
+
+Please visit the AA website and forum at https://aatremulous.ml \ No newline at end of file
diff --git a/src/cgame/cg_animation.c b/src/cgame/cg_animation.c
new file mode 100644
index 0000000..c370c53
--- /dev/null
+++ b/src/cgame/cg_animation.c
@@ -0,0 +1,111 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cg_local.h"
+
+/*
+===============
+CG_RunLerpFrame
+
+Sets cg.snap, cg.oldFrame, and cg.backlerp
+cg.time should be between oldFrameTime and frameTime after exit
+===============
+*/
+void CG_RunLerpFrame( lerpFrame_t *lf )
+{
+ int f, numFrames;
+ animation_t *anim;
+
+ // debugging tool to get no animations
+ if( cg_animSpeed.integer == 0 )
+ {
+ lf->oldFrame = lf->frame = lf->backlerp = 0;
+ return;
+ }
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if( cg.time >= lf->frameTime )
+ {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if( !anim->frameLerp )
+ return; // shouldn't happen
+
+ if( cg.time < lf->animationTime )
+ lf->frameTime = lf->animationTime; // initial lerp
+ else
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ numFrames = anim->numFrames;
+ if( anim->flipflop )
+ numFrames *= 2;
+
+ if( f >= numFrames )
+ {
+ f -= numFrames;
+ if( anim->loopFrames )
+ {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ }
+ else
+ {
+ f = numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = cg.time;
+ }
+ }
+
+ if( anim->reversed )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
+ else if( anim->flipflop && f >= anim->numFrames )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames );
+ else
+ lf->frame = anim->firstFrame + f;
+
+ if( cg.time > lf->frameTime )
+ {
+ lf->frameTime = cg.time;
+ if( cg_debugAnim.integer )
+ CG_Printf( "Clamp lf->frameTime\n" );
+ }
+ }
+
+ if( lf->frameTime > cg.time + 200 )
+ lf->frameTime = cg.time;
+
+ if( lf->oldFrameTime > cg.time )
+ lf->oldFrameTime = cg.time;
+
+ // calculate current lerp value
+ if( lf->frameTime == lf->oldFrameTime )
+ lf->backlerp = 0;
+ else
+ lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+}
diff --git a/src/cgame/cg_animmapobj.c b/src/cgame/cg_animmapobj.c
new file mode 100644
index 0000000..1225314
--- /dev/null
+++ b/src/cgame/cg_animmapobj.c
@@ -0,0 +1,227 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cg_local.h"
+
+
+/*
+===============
+CG_DoorAnimation
+===============
+*/
+static void CG_DoorAnimation( centity_t *cent, int *old, int *now, float *backLerp )
+{
+ CG_RunLerpFrame( &cent->lerpFrame );
+
+ *old = cent->lerpFrame.oldFrame;
+ *now = cent->lerpFrame.frame;
+ *backLerp = cent->lerpFrame.backlerp;
+}
+
+
+/*
+===============
+CG_ModelDoor
+===============
+*/
+void CG_ModelDoor( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es;
+ animation_t anim;
+ lerpFrame_t *lf = &cent->lerpFrame;
+
+ es = &cent->currentState;
+
+ if( !es->modelindex )
+ return;
+
+ //create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ ent.renderfx = RF_NOSHADOW;
+
+ //add the door model
+ ent.skinNum = 0;
+ ent.hModel = cgs.gameModels[ es->modelindex ];
+
+ //scale the door
+ VectorScale( ent.axis[ 0 ], es->origin2[ 0 ], ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], es->origin2[ 1 ], ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], es->origin2[ 2 ], ent.axis[ 2 ] );
+ ent.nonNormalizedAxes = qtrue;
+
+ //setup animation
+ anim.firstFrame = es->misc;
+ anim.numFrames = es->weapon;
+ anim.reversed = !es->legsAnim;
+ anim.flipflop = qfalse;
+ anim.loopFrames = 0;
+ anim.frameLerp = 1000 / es->torsoAnim;
+ anim.initialLerp = 1000 / es->torsoAnim;
+
+ //door changed state
+ if( es->legsAnim != cent->doorState )
+ {
+ lf->animationTime = lf->frameTime + anim.initialLerp;
+ cent->doorState = es->legsAnim;
+ }
+
+ lf->animation = &anim;
+
+ //run animation
+ CG_DoorAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp );
+
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+/*
+===============
+CG_AMOAnimation
+===============
+*/
+static void CG_AMOAnimation( centity_t *cent, int *old, int *now, float *backLerp )
+{
+ if( !( cent->currentState.eFlags & EF_MOVER_STOP ) || cent->animPlaying )
+ {
+ int delta = cg.time - cent->miscTime;
+
+ //hack to prevent "pausing" mucking up the lerping
+ if( delta > 900 )
+ {
+ cent->lerpFrame.oldFrameTime += delta;
+ cent->lerpFrame.frameTime += delta;
+ }
+
+ CG_RunLerpFrame( &cent->lerpFrame );
+ cent->miscTime = cg.time;
+ }
+
+ *old = cent->lerpFrame.oldFrame;
+ *now = cent->lerpFrame.frame;
+ *backLerp = cent->lerpFrame.backlerp;
+}
+
+
+/*
+==================
+CG_animMapObj
+==================
+*/
+void CG_AnimMapObj( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es;
+ float scale;
+ animation_t anim;
+
+ es = &cent->currentState;
+
+ // if set to invisible, skip
+ if( !es->modelindex || ( es->eFlags & EF_NODRAW ) )
+ return;
+
+ memset( &ent, 0, sizeof( ent ) );
+
+ VectorCopy( es->angles, cent->lerpAngles );
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ ent.hModel = cgs.gameModels[ es->modelindex ];
+
+ VectorCopy( cent->lerpOrigin, ent.origin);
+ VectorCopy( cent->lerpOrigin, ent.oldorigin);
+
+ ent.nonNormalizedAxes = qfalse;
+
+ //scale the model
+ if( es->angles2[ 0 ] )
+ {
+ scale = es->angles2[ 0 ];
+ VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] );
+ ent.nonNormalizedAxes = qtrue;
+ }
+
+ //setup animation
+ anim.firstFrame = es->misc;
+ anim.numFrames = es->weapon;
+ anim.reversed = qfalse;
+ anim.flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( anim.numFrames < 0 )
+ {
+ anim.numFrames = -anim.numFrames;
+ anim.reversed = qtrue;
+ }
+
+ anim.loopFrames = es->torsoAnim;
+
+ if( !es->legsAnim )
+ {
+ anim.frameLerp = 1000;
+ anim.initialLerp = 1000;
+ }
+ else
+ {
+ anim.frameLerp = 1000 / es->legsAnim;
+ anim.initialLerp = 1000 / es->legsAnim;
+ }
+
+ cent->lerpFrame.animation = &anim;
+
+ if( !anim.loopFrames )
+ {
+ // add one frame to allow the animation to play the last frame
+ // add another to get it to stop playing at the first frame
+ anim.numFrames += 2;
+
+ if( !cent->animInit )
+ {
+ cent->animInit = qtrue;
+ cent->animPlaying = !( cent->currentState.eFlags & EF_MOVER_STOP );
+ }
+ else
+ {
+ if( cent->animLastState !=
+ !( cent->currentState.eFlags & EF_MOVER_STOP ) )
+ {
+ cent->animPlaying = qtrue;
+ cent->lerpFrame.animationTime = cg.time;
+ cent->lerpFrame.frameTime = cg.time;
+ }
+ }
+ cent->animLastState = !( cent->currentState.eFlags & EF_MOVER_STOP );
+ }
+
+ //run animation
+ CG_AMOAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp );
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene(&ent);
+}
diff --git a/src/cgame/cg_attachment.c b/src/cgame/cg_attachment.c
new file mode 100644
index 0000000..0d3c8ea
--- /dev/null
+++ b/src/cgame/cg_attachment.c
@@ -0,0 +1,404 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_attachment.c -- an abstract attachment system
+
+#include "cg_local.h"
+
+/*
+===============
+CG_AttachmentPoint
+
+Return the attachment point
+===============
+*/
+qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v )
+{
+ centity_t *cent;
+
+ if( !a )
+ return qfalse;
+
+ // if it all breaks, then use the last point we know was correct
+ VectorCopy( a->lastValidAttachmentPoint, v );
+
+ switch( a->type )
+ {
+ case AT_STATIC:
+ if( !a->staticValid )
+ return qfalse;
+
+ VectorCopy( a->origin, v );
+ break;
+
+ case AT_TAG:
+ if( !a->tagValid )
+ return qfalse;
+
+ AxisCopy( axisDefault, a->re.axis );
+ CG_PositionRotatedEntityOnTag( &a->re, &a->parent,
+ a->model, a->tagName );
+ VectorCopy( a->re.origin, v );
+ break;
+
+ case AT_CENT:
+ if( !a->centValid )
+ return qfalse;
+
+ if( a->centNum == cg.predictedPlayerState.clientNum )
+ {
+ // this is smoother if it's the local client
+ VectorCopy( cg.predictedPlayerState.origin, v );
+ }
+ else
+ {
+ cent = &cg_entities[ a->centNum ];
+ VectorCopy( cent->lerpOrigin, v );
+ }
+ break;
+
+ case AT_PARTICLE:
+ if( !a->particleValid )
+ return qfalse;
+
+ if( !a->particle->valid )
+ {
+ a->particleValid = qfalse;
+ return qfalse;
+ }
+ else
+ VectorCopy( a->particle->origin, v );
+ break;
+
+ default:
+ CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" );
+ break;
+ }
+
+ if( a->hasOffset )
+ VectorAdd( v, a->offset, v );
+
+ VectorCopy( v, a->lastValidAttachmentPoint );
+
+ return qtrue;
+}
+
+/*
+===============
+CG_AttachmentDir
+
+Return the attachment direction
+===============
+*/
+qboolean CG_AttachmentDir( attachment_t *a, vec3_t v )
+{
+ vec3_t forward;
+ centity_t *cent;
+
+ if( !a )
+ return qfalse;
+
+ switch( a->type )
+ {
+ case AT_STATIC:
+ return qfalse;
+ break;
+
+ case AT_TAG:
+ if( !a->tagValid )
+ return qfalse;
+
+ VectorCopy( a->re.axis[ 0 ], v );
+ break;
+
+ case AT_CENT:
+ if( !a->centValid )
+ return qfalse;
+
+ cent = &cg_entities[ a->centNum ];
+ AngleVectors( cent->lerpAngles, forward, NULL, NULL );
+ VectorCopy( forward, v );
+ break;
+
+ case AT_PARTICLE:
+ if( !a->particleValid )
+ return qfalse;
+
+ if( !a->particle->valid )
+ {
+ a->particleValid = qfalse;
+ return qfalse;
+ }
+ else
+ VectorCopy( a->particle->velocity, v );
+ break;
+
+ default:
+ CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" );
+ break;
+ }
+
+ VectorNormalize( v );
+ return qtrue;
+}
+
+/*
+===============
+CG_AttachmentAxis
+
+Return the attachment axis
+===============
+*/
+qboolean CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] )
+{
+ centity_t *cent;
+
+ if( !a )
+ return qfalse;
+
+ switch( a->type )
+ {
+ case AT_STATIC:
+ return qfalse;
+ break;
+
+ case AT_TAG:
+ if( !a->tagValid )
+ return qfalse;
+
+ AxisCopy( a->re.axis, axis );
+ break;
+
+ case AT_CENT:
+ if( !a->centValid )
+ return qfalse;
+
+ cent = &cg_entities[ a->centNum ];
+ AnglesToAxis( cent->lerpAngles, axis );
+ break;
+
+ case AT_PARTICLE:
+ return qfalse;
+ break;
+
+ default:
+ CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" );
+ break;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_AttachmentVelocity
+
+If the attachment can have velocity, return it
+===============
+*/
+qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v )
+{
+ if( !a )
+ return qfalse;
+
+ if( a->particleValid && a->particle->valid )
+ {
+ VectorCopy( a->particle->velocity, v );
+ return qtrue;
+ }
+ else if( a->centValid )
+ {
+ centity_t *cent = &cg_entities[ a->centNum ];
+
+ VectorCopy( cent->currentState.pos.trDelta, v );
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_AttachmentCentNum
+
+If the attachment has a centNum, return it
+===============
+*/
+int CG_AttachmentCentNum( attachment_t *a )
+{
+ if( !a || !a->centValid )
+ return -1;
+
+ return a->centNum;
+}
+
+/*
+===============
+CG_Attached
+
+If the attachment is valid, return qtrue
+===============
+*/
+qboolean CG_Attached( attachment_t *a )
+{
+ if( !a )
+ return qfalse;
+
+ return a->attached;
+}
+
+/*
+===============
+CG_AttachToPoint
+
+Attach to a point in space
+===============
+*/
+void CG_AttachToPoint( attachment_t *a )
+{
+ if( !a || !a->staticValid )
+ return;
+
+ a->type = AT_STATIC;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_AttachToCent
+
+Attach to a centity_t
+===============
+*/
+void CG_AttachToCent( attachment_t *a )
+{
+ if( !a || !a->centValid )
+ return;
+
+ a->type = AT_CENT;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_AttachToTag
+
+Attach to a model tag
+===============
+*/
+void CG_AttachToTag( attachment_t *a )
+{
+ if( !a || !a->tagValid )
+ return;
+
+ a->type = AT_TAG;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_AttachToParticle
+
+Attach to a particle
+===============
+*/
+void CG_AttachToParticle( attachment_t *a )
+{
+ if( !a || !a->particleValid )
+ return;
+
+ a->type = AT_PARTICLE;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentPoint
+===============
+*/
+void CG_SetAttachmentPoint( attachment_t *a, vec3_t v )
+{
+ if( !a )
+ return;
+
+ VectorCopy( v, a->origin );
+ a->staticValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentCent
+===============
+*/
+void CG_SetAttachmentCent( attachment_t *a, centity_t *cent )
+{
+ if( !a || !cent )
+ return;
+
+ a->centNum = cent->currentState.number;
+ a->centValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentTag
+===============
+*/
+void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent,
+ qhandle_t model, char *tagName )
+{
+ if( !a )
+ return;
+
+ a->parent = parent;
+ a->model = model;
+ strncpy( a->tagName, tagName, MAX_STRING_CHARS );
+ a->tagValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentParticle
+===============
+*/
+void CG_SetAttachmentParticle( attachment_t *a, particle_t *p )
+{
+ if( !a )
+ return;
+
+ a->particle = p;
+ a->particleValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentOffset
+===============
+*/
+void CG_SetAttachmentOffset( attachment_t *a, vec3_t v )
+{
+ if( !a )
+ return;
+
+ VectorCopy( v, a->offset );
+ a->hasOffset = qtrue;
+}
diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c
new file mode 100644
index 0000000..e678ebd
--- /dev/null
+++ b/src/cgame/cg_buildable.c
@@ -0,0 +1,1423 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#include "cg_local.h"
+
+char *cg_buildableSoundNames[ MAX_BUILDABLE_ANIMATIONS ] =
+{
+ "construct1.wav",
+ "construct2.wav",
+ "idle1.wav",
+ "idle2.wav",
+ "idle3.wav",
+ "attack1.wav",
+ "attack2.wav",
+ "spawn1.wav",
+ "spawn2.wav",
+ "pain1.wav",
+ "pain2.wav",
+ "destroy1.wav",
+ "destroy2.wav",
+ "destroyed.wav"
+};
+
+static sfxHandle_t defaultAlienSounds[ MAX_BUILDABLE_ANIMATIONS ];
+static sfxHandle_t defaultHumanSounds[ MAX_BUILDABLE_ANIMATIONS ];
+
+/*
+===================
+CG_AlienBuildableExplosion
+
+Generated a bunch of gibs launching out from a location
+===================
+*/
+void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir )
+{
+ particleSystem_t *ps;
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.alienBuildableExplosion );
+
+ //particle system
+ ps = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDestroyedPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, origin );
+ CG_SetParticleSystemNormal( ps, dir );
+ CG_AttachToPoint( &ps->attachment );
+ }
+}
+
+/*
+=================
+CG_HumanBuildableExplosion
+
+Called for human buildables as they are destroyed
+=================
+*/
+void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir )
+{
+ particleSystem_t *ps;
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.humanBuildableExplosion );
+
+ //particle system
+ ps = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDestroyedPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, origin );
+ CG_SetParticleSystemNormal( ps, dir );
+ CG_AttachToPoint( &ps->attachment );
+ }
+}
+
+
+#define CREEP_SIZE 64.0f
+
+/*
+==================
+CG_Creep
+==================
+*/
+static void CG_Creep( centity_t *cent )
+{
+ int msec;
+ float size, frac;
+ trace_t tr;
+ vec3_t temp, origin;
+ int scaleUpTime = BG_FindBuildTimeForBuildable( cent->currentState.modelindex );
+ int time;
+
+ time = cent->currentState.time;
+
+ //should the creep be growing or receding?
+ if( time >= 0 )
+ {
+ msec = cg.time - time;
+ if( msec >= 0 && msec < scaleUpTime )
+ frac = (float)msec / scaleUpTime;
+ else
+ frac = 1.0f;
+ }
+ else
+ {
+ msec = cg.time + time;
+ if( msec >= 0 && msec < CREEP_SCALEDOWN_TIME )
+ frac = 1.0f - ( (float)msec / CREEP_SCALEDOWN_TIME );
+ else
+ frac = 0.0f;
+ }
+
+ VectorCopy( cent->currentState.origin2, temp );
+ VectorScale( temp, -4096, temp );
+ VectorAdd( temp, cent->lerpOrigin, temp );
+
+ CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, temp, cent->currentState.number, MASK_PLAYERSOLID );
+
+ VectorCopy( tr.endpos, origin );
+
+ size = CREEP_SIZE * frac;
+
+ if( size > 0.0f )
+ CG_ImpactMark( cgs.media.creepShader, origin, cent->currentState.origin2,
+ 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue );
+}
+
+/*
+======================
+CG_ParseBuildableAnimationFile
+
+Read a configuration file containing animation counts and rates
+models/buildables/hivemind/animation.cfg, etc
+======================
+*/
+static qboolean CG_ParseBuildableAnimationFile( const char *filename, buildable_t buildable )
+{
+ char *text_p;
+ int len;
+ int i;
+ char *token;
+ float fps;
+ char text[ 20000 ];
+ fileHandle_t f;
+ animation_t *animations;
+
+ animations = cg_buildables[ buildable ].animations;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read information for each frame
+ for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ )
+ {
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].firstFrame = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].numFrames = atoi( token );
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( animations[ i ].numFrames < 0 )
+ {
+ animations[ i ].numFrames = -animations[ i ].numFrames;
+ animations[ i ].reversed = qtrue;
+ }
+
+ token = COM_Parse( &text_p );
+ if ( !*token )
+ break;
+
+ animations[i].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ fps = atof( token );
+ if( fps == 0 )
+ fps = 1;
+
+ animations[ i ].frameLerp = 1000 / fps;
+ animations[ i ].initialLerp = 1000 / fps;
+ }
+
+ if( i != MAX_BUILDABLE_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing animation file: %s\n", filename );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+======================
+CG_ParseBuildableSoundFile
+
+Read a configuration file containing sound properties
+sound/buildables/hivemind/sound.cfg, etc
+======================
+*/
+static qboolean CG_ParseBuildableSoundFile( const char *filename, buildable_t buildable )
+{
+ char *text_p;
+ int len;
+ int i;
+ char *token;
+ char text[ 20000 ];
+ fileHandle_t f;
+ sound_t *sounds;
+
+ sounds = cg_buildables[ buildable ].sounds;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( len <= 0 )
+ return qfalse;
+
+ if ( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read information for each frame
+ for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ )
+ {
+
+ token = COM_Parse( &text_p );
+ if ( !*token )
+ break;
+
+ sounds[ i ].enabled = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if ( !*token )
+ break;
+
+ sounds[ i ].looped = atoi( token );
+
+ }
+
+ if( i != MAX_BUILDABLE_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing sound file: %s\n", filename );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+/*
+===============
+CG_InitBuildables
+
+Initialises the animation db
+===============
+*/
+void CG_InitBuildables( void )
+{
+ char filename[ MAX_QPATH ];
+ char soundfile[ MAX_QPATH ];
+ char *buildableName;
+ char *modelFile;
+ int i;
+ int j;
+ fileHandle_t f;
+
+ memset( cg_buildables, 0, sizeof( cg_buildables ) );
+
+ //default sounds
+ for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ )
+ {
+ strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] );
+
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/alien/%s", soundfile );
+ defaultAlienSounds[ j ] = trap_S_RegisterSound( filename, qfalse );
+
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/human/%s", soundfile );
+ defaultHumanSounds[ j ] = trap_S_RegisterSound( filename, qfalse );
+ }
+
+ cg.buildablesFraction = 0.0f;
+
+ for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ buildableName = BG_FindNameForBuildable( i );
+
+ //animation.cfg
+ Com_sprintf( filename, sizeof( filename ), "models/buildables/%s/animation.cfg", buildableName );
+ if ( !CG_ParseBuildableAnimationFile( filename, i ) )
+ Com_Printf( S_COLOR_YELLOW "WARNING: failed to load animation file %s\n", filename );
+
+ //sound.cfg
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/sound.cfg", buildableName );
+ if ( !CG_ParseBuildableSoundFile( filename, i ) )
+ Com_Printf( S_COLOR_YELLOW "WARNING: failed to load sound file %s\n", filename );
+
+ //models
+ for( j = 0; j <= 3; j++ )
+ {
+ if( ( modelFile = BG_FindModelsForBuildable( i, j ) ) )
+ cg_buildables[ i ].models[ j ] = trap_R_RegisterModel( modelFile );
+ }
+
+ //sounds
+ for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ )
+ {
+ strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] );
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/%s", buildableName, soundfile );
+
+ if( cg_buildables[ i ].sounds[ j ].enabled )
+ {
+ if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 )
+ {
+ //file exists so close it
+ trap_FS_FCloseFile( f );
+
+ cg_buildables[ i ].sounds[ j ].sound = trap_S_RegisterSound( filename, qfalse );
+ }
+ else
+ {
+ //file doesn't exist - use default
+ if( BG_FindTeamForBuildable( i ) == BIT_ALIENS )
+ cg_buildables[ i ].sounds[ j ].sound = defaultAlienSounds[ j ];
+ else
+ cg_buildables[ i ].sounds[ j ].sound = defaultHumanSounds[ j ];
+ }
+ }
+ }
+
+ cg.buildablesFraction = (float)i / (float)( BA_NUM_BUILDABLES - 1 );
+ trap_UpdateScreen( );
+ }
+
+ cgs.media.teslaZapTS = CG_RegisterTrailSystem( "models/buildables/tesla/zap" );
+}
+
+/*
+===============
+CG_SetBuildableLerpFrameAnimation
+
+may include ANIM_TOGGLEBIT
+===============
+*/
+static void CG_SetBuildableLerpFrameAnimation( buildable_t buildable, lerpFrame_t *lf, int newAnimation )
+{
+ animation_t *anim;
+
+ lf->animationNumber = newAnimation;
+
+ if( newAnimation < 0 || newAnimation >= MAX_BUILDABLE_ANIMATIONS )
+ CG_Error( "Bad animation number: %i", newAnimation );
+
+ anim = &cg_buildables[ buildable ].animations[ newAnimation ];
+
+ //this item has just spawned so lf->frameTime will be zero
+ if( !lf->animation )
+ lf->frameTime = cg.time + 1000; //1 sec delay before starting the spawn anim
+
+ lf->animation = anim;
+ lf->animationTime = lf->frameTime + anim->initialLerp;
+
+ if( cg_debugAnim.integer )
+ CG_Printf( "Anim: %i\n", newAnimation );
+}
+
+/*
+===============
+CG_RunBuildableLerpFrame
+
+Sets cg.snap, cg.oldFrame, and cg.backlerp
+cg.time should be between oldFrameTime and frameTime after exit
+===============
+*/
+static void CG_RunBuildableLerpFrame( centity_t *cent )
+{
+ int f, numFrames;
+ buildable_t buildable = cent->currentState.modelindex;
+ lerpFrame_t *lf = &cent->lerpFrame;
+ animation_t *anim;
+ buildableAnimNumber_t newAnimation = cent->buildableAnim & ~( ANIM_TOGGLEBIT|ANIM_FORCEBIT );
+
+ // debugging tool to get no animations
+ if( cg_animSpeed.integer == 0 )
+ {
+ lf->oldFrame = lf->frame = lf->backlerp = 0;
+ return;
+ }
+
+ // see if the animation sequence is switching
+ if( newAnimation != lf->animationNumber || !lf->animation )
+ {
+ if( cg_debugRandom.integer )
+ CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: %d\n",
+ newAnimation, lf->animationNumber, lf->animation );
+
+ CG_SetBuildableLerpFrameAnimation( buildable, lf, newAnimation );
+
+ if( !cg_buildables[ buildable ].sounds[ newAnimation ].looped &&
+ cg_buildables[ buildable ].sounds[ newAnimation ].enabled )
+ {
+ if( cg_debugRandom.integer )
+ CG_Printf( "Sound for animation %d for a %s\n",
+ newAnimation, BG_FindHumanNameForBuildable( buildable ) );
+
+ trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO,
+ cg_buildables[ buildable ].sounds[ newAnimation ].sound );
+ }
+ }
+
+ if( cg_buildables[ buildable ].sounds[ lf->animationNumber ].looped &&
+ cg_buildables[ buildable ].sounds[ lf->animationNumber ].enabled )
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ cg_buildables[ buildable ].sounds[ lf->animationNumber ].sound );
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if( cg.time >= lf->frameTime )
+ {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if( !anim->frameLerp )
+ return; // shouldn't happen
+
+ if ( cg.time < lf->animationTime )
+ lf->frameTime = lf->animationTime; // initial lerp
+ else
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ numFrames = anim->numFrames;
+ if(anim->flipflop)
+ numFrames *= 2;
+
+ if( f >= numFrames )
+ {
+ f -= numFrames;
+ if( anim->loopFrames )
+ {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ }
+ else
+ {
+ f = numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = cg.time;
+ cent->buildableAnim = cent->currentState.torsoAnim;
+ }
+ }
+
+ if( anim->reversed )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
+ else if( anim->flipflop && f >= anim->numFrames )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames );
+ else
+ lf->frame = anim->firstFrame + f;
+
+ if( cg.time > lf->frameTime )
+ {
+ lf->frameTime = cg.time;
+ if( cg_debugAnim.integer )
+ CG_Printf( "Clamp lf->frameTime\n");
+ }
+ }
+
+ if( lf->frameTime > cg.time + 200 )
+ lf->frameTime = cg.time;
+
+ if( lf->oldFrameTime > cg.time )
+ lf->oldFrameTime = cg.time;
+
+ // calculate current lerp value
+ if( lf->frameTime == lf->oldFrameTime )
+ lf->backlerp = 0;
+ else
+ lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+}
+
+/*
+===============
+CG_BuildableAnimation
+===============
+*/
+static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *backLerp )
+{
+ entityState_t *es = &cent->currentState;
+
+ //if no animation is set default to idle anim
+ if( cent->buildableAnim == BANIM_NONE )
+ cent->buildableAnim = es->torsoAnim;
+
+ //display the first frame of the construction anim if not yet spawned
+ if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ {
+ animation_t *anim = &cg_buildables[ es->modelindex ].animations[ BANIM_CONSTRUCT1 ];
+
+ //so that when animation starts for real it has sensible numbers
+ cent->lerpFrame.oldFrameTime =
+ cent->lerpFrame.frameTime =
+ cent->lerpFrame.animationTime =
+ cg.time;
+
+ *old = cent->lerpFrame.oldFrame = anim->firstFrame;
+ *now = cent->lerpFrame.frame = anim->firstFrame;
+ *backLerp = cent->lerpFrame.backlerp = 0.0f;
+
+ //ensure that an animation is triggered once the buildable has spawned
+ cent->oldBuildableAnim = BANIM_NONE;
+ }
+ else
+ {
+ if( ( cent->oldBuildableAnim ^ es->legsAnim ) & ANIM_TOGGLEBIT )
+ {
+ if( cg_debugAnim.integer )
+ CG_Printf( "%d->%d l:%d t:%d %s(%d)\n",
+ cent->oldBuildableAnim, cent->buildableAnim,
+ es->legsAnim, es->torsoAnim,
+ BG_FindHumanNameForBuildable( es->modelindex ), es->number );
+
+ if( cent->buildableAnim == es->torsoAnim || es->legsAnim & ANIM_FORCEBIT )
+ cent->buildableAnim = cent->oldBuildableAnim = es->legsAnim;
+ else
+ cent->buildableAnim = cent->oldBuildableAnim = es->torsoAnim;
+ }
+
+ CG_RunBuildableLerpFrame( cent );
+
+ *old = cent->lerpFrame.oldFrame;
+ *now = cent->lerpFrame.frame;
+ *backLerp = cent->lerpFrame.backlerp;
+ }
+}
+
+#define TRACE_DEPTH 64.0f
+
+/*
+===============
+CG_PositionAndOrientateBuildable
+===============
+*/
+static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t inOrigin,
+ const vec3_t normal, const int skipNumber,
+ const vec3_t mins, const vec3_t maxs,
+ vec3_t outAxis[ 3 ], vec3_t outOrigin )
+{
+ vec3_t forward, start, end;
+ trace_t tr;
+
+ AngleVectors( angles, forward, NULL, NULL );
+ VectorCopy( normal, outAxis[ 2 ] );
+ ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] );
+
+ if( !VectorNormalize( outAxis[ 0 ] ) )
+ {
+ AngleVectors( angles, NULL, NULL, forward );
+ ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] );
+ VectorNormalize( outAxis[ 0 ] );
+ }
+
+ CrossProduct( outAxis[ 0 ], outAxis[ 2 ], outAxis[ 1 ] );
+ outAxis[ 1 ][ 0 ] = -outAxis[ 1 ][ 0 ];
+ outAxis[ 1 ][ 1 ] = -outAxis[ 1 ][ 1 ];
+ outAxis[ 1 ][ 2 ] = -outAxis[ 1 ][ 2 ];
+
+ VectorMA( inOrigin, -TRACE_DEPTH, normal, end );
+ VectorMA( inOrigin, 1.0f, normal, start );
+ CG_CapTrace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID );
+
+ if( tr.fraction == 1.0f )
+ {
+ //erm we missed completely - try again with a box trace
+ CG_Trace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID );
+ }
+
+ VectorMA( inOrigin, tr.fraction * -TRACE_DEPTH, normal, outOrigin );
+}
+
+/*
+==================
+CG_GhostBuildable
+==================
+*/
+void CG_GhostBuildable( buildable_t buildable )
+{
+ refEntity_t ent;
+ playerState_t *ps;
+ vec3_t angles, entity_origin;
+ vec3_t mins, maxs;
+ trace_t tr;
+ float scale;
+
+ ps = &cg.predictedPlayerState;
+
+ memset( &ent, 0, sizeof( ent ) );
+
+ BG_FindBBoxForBuildable( buildable, mins, maxs );
+
+ BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, CG_Trace, entity_origin, angles, &tr );
+
+ CG_PositionAndOrientateBuildable( ps->viewangles, entity_origin, tr.plane.normal, ps->clientNum,
+ mins, maxs, ent.axis, ent.origin );
+
+ //offset on the Z axis if required
+ VectorMA( ent.origin, BG_FindZOffsetForBuildable( buildable ), tr.plane.normal, ent.origin );
+
+ VectorCopy( ent.origin, ent.lightingOrigin );
+ VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all
+
+ ent.hModel = cg_buildables[ buildable ].models[ 0 ];
+
+ if( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT )
+ ent.customShader = cgs.media.greenBuildShader;
+ else
+ ent.customShader = cgs.media.redBuildShader;
+
+ //rescale the model
+ scale = BG_FindModelScaleForBuildable( buildable );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] );
+
+ ent.nonNormalizedAxes = qtrue;
+ }
+ else
+ ent.nonNormalizedAxes = qfalse;
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+/*
+==================
+CG_BuildableParticleEffects
+==================
+*/
+static void CG_BuildableParticleEffects( centity_t *cent )
+{
+ entityState_t *es = &cent->currentState;
+ buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex );
+ int health = es->generic1 & B_HEALTH_MASK;
+ float healthFrac = (float)health / B_HEALTH_MASK;
+
+ if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ return;
+
+ if( team == BIT_HUMANS )
+ {
+ if( healthFrac < 0.33f && !CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDamagedPS );
+
+ if( CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ CG_SetAttachmentCent( &cent->buildablePS->attachment, cent );
+ CG_AttachToCent( &cent->buildablePS->attachment );
+ }
+ }
+ else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( &cent->buildablePS ) )
+ CG_DestroyParticleSystem( &cent->buildablePS );
+ }
+ else if( team == BIT_ALIENS )
+ {
+ if( healthFrac < 0.33f && !CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDamagedPS );
+
+ if( CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ CG_SetAttachmentCent( &cent->buildablePS->attachment, cent );
+ CG_SetParticleSystemNormal( cent->buildablePS, es->origin2 );
+ CG_AttachToCent( &cent->buildablePS->attachment );
+ }
+ }
+ else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( &cent->buildablePS ) )
+ CG_DestroyParticleSystem( &cent->buildablePS );
+ }
+}
+
+
+void CG_BuildableStatusParse( const char *filename, buildStat_t *bs )
+{
+ pc_token_t token;
+ int handle;
+ const char *s;
+ int i;
+ float f;
+ vec4_t c;
+
+ handle = trap_Parse_LoadSource( filename );
+ if( !handle )
+ return;
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+ if( !Q_stricmp( token.string, "frameShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->frameShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "overlayShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->overlayShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "noPowerShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->noPowerShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "markedShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->markedShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthSevereColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthSevereColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthHighColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthHighColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthElevatedColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthElevatedColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthGuardedColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthGuardedColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthLowColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthLowColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "foreColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->foreColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "backColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->backColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "frameHeight" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->frameHeight = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "frameWidth" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->frameWidth = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthPadding" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->healthPadding = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "overlayHeight" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->overlayHeight = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "overlayWidth" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->overlayWidth = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "verticalMargin" ) )
+ {
+ if( PC_Float_Parse( handle, &f ) )
+ bs->verticalMargin = f;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "horizontalMargin" ) )
+ {
+ if( PC_Float_Parse( handle, &f ) )
+ bs->horizontalMargin = f;
+ continue;
+ }
+ else
+ {
+ Com_Printf("CG_BuildableStatusParse: unknown token %s in %s\n",
+ token.string, filename );
+ bs->loaded = qfalse;
+ return;
+ }
+ }
+ bs->loaded = qtrue;
+}
+
+#define STATUS_FADE_TIME 200
+#define STATUS_MAX_VIEW_DIST 900.0f
+#define STATUS_PEEK_DIST 20
+/*
+==================
+CG_BuildableStatusDisplay
+==================
+*/
+static void CG_BuildableStatusDisplay( centity_t *cent )
+{
+ entityState_t *es = &cent->currentState;
+ vec3_t origin;
+ float healthScale;
+ int health;
+ float x, y;
+ vec4_t color;
+ qboolean powered, marked;
+ trace_t tr;
+ float d;
+ buildStat_t *bs;
+ int i, j;
+ int entNum;
+ vec3_t trOrigin;
+ vec3_t right;
+ qboolean visible = qfalse;
+ vec3_t mins, maxs;
+ entityState_t *hit;
+
+ if( BG_FindTeamForBuildable( es->modelindex ) == BIT_ALIENS )
+ bs = &cgs.alienBuildStat;
+ else
+ bs = &cgs.humanBuildStat;
+
+ if( !bs->loaded )
+ return;
+
+ d = Distance( cent->lerpOrigin, cg.refdef.vieworg );
+ if( d > STATUS_MAX_VIEW_DIST )
+ return;
+
+ Vector4Copy( bs->foreColor, color );
+
+ // trace for center point
+ BG_FindBBoxForBuildable( es->modelindex, mins, maxs );
+
+ VectorCopy( cent->lerpOrigin, origin );
+
+ // center point
+ origin[ 2 ] += mins[ 2 ];
+ origin[ 2 ] += ( fabs( mins[ 2 ] ) + fabs( maxs[ 2 ] ) ) / 2;
+
+ entNum = cg.predictedPlayerState.clientNum;
+
+ // if first try fails, step left, step right
+ for( j = 0; j < 3; j++ )
+ {
+ VectorCopy( cg.refdef.vieworg, trOrigin );
+ switch( j )
+ {
+ case 1:
+ // step right
+ AngleVectors( cg.refdefViewAngles, NULL, right, NULL );
+ VectorMA( trOrigin, STATUS_PEEK_DIST, right, trOrigin );
+ break;
+ case 2:
+ // step left
+ AngleVectors( cg.refdefViewAngles, NULL, right, NULL );
+ VectorMA( trOrigin, -STATUS_PEEK_DIST, right, trOrigin );
+ break;
+ default:
+ break;
+ }
+ // look through up to 3 players and/or transparent buildables
+ for( i = 0; i < 3; i++ )
+ {
+ CG_Trace( &tr, trOrigin, NULL, NULL, origin, entNum, MASK_SHOT );
+ if( tr.entityNum == cent->currentState.number )
+ {
+ visible = qtrue;
+ break;
+ }
+
+ if( tr.entityNum == ENTITYNUM_WORLD )
+ break;
+
+ hit = &cg_entities[ tr.entityNum ].currentState;
+
+ if( tr.entityNum < MAX_CLIENTS || ( hit->eType == ET_BUILDABLE &&
+ ( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ||
+ BG_FindTransparentTestForBuildable( hit->modelindex ) ) ) )
+ {
+ entNum = tr.entityNum;
+ VectorCopy( tr.endpos, trOrigin );
+ }
+ else
+ break;
+ }
+ }
+ // hack to make the kit obscure view
+ if( cg_drawGun.integer && visible &&
+ cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ CG_WorldToScreen( origin, &x, &y ) )
+ {
+ if( x > 450 && y > 290 )
+ visible = qfalse;
+ }
+
+ if( !visible && cent->buildableStatus.visible )
+ {
+ cent->buildableStatus.visible = qfalse;
+ cent->buildableStatus.lastTime = cg.time;
+ }
+ else if( visible && !cent->buildableStatus.visible )
+ {
+ cent->buildableStatus.visible = qtrue;
+ cent->buildableStatus.lastTime = cg.time;
+ }
+
+ // Fade up
+ if( cent->buildableStatus.visible )
+ {
+ if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time )
+ color[ 3 ] = (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME;
+ }
+
+ // Fade down
+ if( !cent->buildableStatus.visible )
+ {
+ if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time )
+ color[ 3 ] = 1.0f - (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME;
+ else
+ return;
+ }
+
+ health = es->generic1 & B_HEALTH_MASK;
+ healthScale = (float)health / B_HEALTH_MASK;
+
+ if( health > 0 && healthScale < 0.01f )
+ healthScale = 0.01f;
+ else if( healthScale < 0.0f )
+ healthScale = 0.0f;
+ else if( healthScale > 1.0f )
+ healthScale = 1.0f;
+
+ if( CG_WorldToScreen( origin, &x, &y ) )
+ {
+ float picH = bs->frameHeight;
+ float picW = bs->frameWidth;
+ float picX = x;
+ float picY = y;
+ float scale;
+ float subH, subY;
+ vec4_t frameColor;
+
+ // this is fudged to get the width/height in the cfg to be more realistic
+ scale = ( picH / d ) * 3;
+
+ powered = es->generic1 & B_POWERED_TOGGLEBIT;
+ marked = es->generic1 & B_MARKED_TOGGLEBIT;
+
+ picH *= scale;
+ picW *= scale;
+ picX -= ( picW * 0.5f );
+ picY -= ( picH * 0.5f );
+
+ // sub-elements such as icons and number
+ subH = picH - ( picH * bs->verticalMargin );
+ subY = picY + ( picH * 0.5f ) - ( subH * 0.5f );
+
+ if( bs->frameShader )
+ {
+ Vector4Copy( bs->backColor, frameColor );
+ frameColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( frameColor );
+ CG_DrawPic( picX, picY, picW, picH, bs->frameShader );
+ trap_R_SetColor( NULL );
+ }
+
+ if( health > 0 )
+ {
+ float hX, hY, hW, hH;
+ vec4_t healthColor;
+
+ hX = picX + ( bs->healthPadding * scale );
+ hY = picY + ( bs->healthPadding * scale );
+ hH = picH - ( bs->healthPadding * 2.0f * scale );
+ hW = picW * healthScale - ( bs->healthPadding * 2.0f * scale );
+
+ if( healthScale == 1.0f )
+ Vector4Copy( bs->healthLowColor, healthColor );
+ else if( healthScale >= 0.75f )
+ Vector4Copy( bs->healthGuardedColor, healthColor );
+ else if( healthScale >= 0.50f )
+ Vector4Copy( bs->healthElevatedColor, healthColor );
+ else if( healthScale >= 0.25f )
+ Vector4Copy( bs->healthHighColor, healthColor );
+ else
+ Vector4Copy( bs->healthSevereColor, healthColor );
+
+ healthColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( healthColor );
+
+ CG_DrawPic( hX, hY, hW, hH, cgs.media.whiteShader );
+ trap_R_SetColor( NULL );
+ }
+
+ if( bs->overlayShader )
+ {
+ float oW = bs->overlayWidth;
+ float oH = bs->overlayHeight;
+ float oX = x;
+ float oY = y;
+
+ oH *= scale;
+ oW *= scale;
+ oX -= ( oW * 0.5f );
+ oY -= ( oH * 0.5f );
+
+ trap_R_SetColor( frameColor );
+ CG_DrawPic( oX, oY, oW, oH, bs->overlayShader );
+ trap_R_SetColor( NULL );
+ }
+
+ trap_R_SetColor( color );
+ if( !powered )
+ {
+ float pX;
+
+ pX = picX + ( subH * bs->horizontalMargin );
+ CG_DrawPic( pX, subY, subH, subH, bs->noPowerShader );
+ }
+
+ if( marked )
+ {
+ float mX;
+
+ mX = picX + picW - ( subH * bs->horizontalMargin ) - subH;
+ CG_DrawPic( mX, subY, subH, subH, bs->markedShader );
+ }
+
+ {
+ float nX;
+ int healthMax;
+ int healthPoints;
+
+ healthMax = BG_FindHealthForBuildable( es->modelindex );
+ healthPoints = (int)( healthScale * healthMax );
+ if( health > 0 && healthPoints < 1 )
+ healthPoints = 1;
+ nX = picX + ( picW * 0.5f ) - 2.0f - ( ( subH * 4 ) * 0.5f );
+
+ if( healthPoints > 999 )
+ nX -= 0.0f;
+ else if( healthPoints > 99 )
+ nX -= subH * 0.5f;
+ else if( healthPoints > 9 )
+ nX -= subH * 1.0f;
+ else
+ nX -= subH * 1.5f;
+
+ CG_DrawField( nX, subY, 4, subH, subH, healthPoints );
+ }
+ trap_R_SetColor( NULL );
+ }
+}
+
+static int QDECL CG_SortDistance( const void *a, const void *b )
+{
+ centity_t *aent, *bent;
+ float adist, bdist;
+
+ aent = &cg_entities[ *(int *)a ];
+ bent = &cg_entities[ *(int *)b ];
+ adist = Distance( cg.refdef.vieworg, aent->lerpOrigin );
+ bdist = Distance( cg.refdef.vieworg, bent->lerpOrigin );
+ if( adist > bdist )
+ return -1;
+ else if( adist < bdist )
+ return 1;
+ else
+ return 0;
+}
+
+/*
+==================
+CG_DrawBuildableStatus
+==================
+*/
+void CG_DrawBuildableStatus( void )
+{
+ int i;
+ centity_t *cent;
+ entityState_t *es;
+ int buildableList[ MAX_ENTITIES_IN_SNAPSHOT ];
+ int buildables = 0;
+
+ switch( cg.predictedPlayerState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+ es = &cent->currentState;
+
+ if( es->eType == ET_BUILDABLE &&
+ BG_FindTeamForBuildable( es->modelindex ) ==
+ BG_FindTeamForWeapon( cg.predictedPlayerState.weapon ) )
+ buildableList[ buildables++ ] = cg.snap->entities[ i ].number;
+ }
+ qsort( buildableList, buildables, sizeof( int ), CG_SortDistance );
+ for( i = 0; i < buildables; i++ )
+ CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ] );
+ break;
+
+ default:
+ break;
+ }
+}
+
+#define BUILDABLE_SOUND_PERIOD 500
+
+/*
+==================
+CG_Buildable
+==================
+*/
+void CG_Buildable( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es = &cent->currentState;
+ vec3_t angles;
+ vec3_t surfNormal, xNormal, mins, maxs;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ float rotAngle;
+ buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex );
+ float scale;
+ int health;
+ float healthScale;
+
+ //must be before EF_NODRAW check
+ if( team == BIT_ALIENS )
+ CG_Creep( cent );
+
+ // if set to invisible, skip
+ if( es->eFlags & EF_NODRAW )
+ {
+ if( CG_IsParticleSystemValid( &cent->buildablePS ) )
+ CG_DestroyParticleSystem( &cent->buildablePS );
+
+ return;
+ }
+
+ memset ( &ent, 0, sizeof( ent ) );
+
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+ VectorCopy( cent->lerpOrigin, ent.lightingOrigin );
+
+ VectorCopy( es->origin2, surfNormal );
+
+ VectorCopy( es->angles, angles );
+ BG_FindBBoxForBuildable( es->modelindex, mins, maxs );
+
+ if( es->pos.trType == TR_STATIONARY )
+ CG_PositionAndOrientateBuildable( angles, ent.origin, surfNormal, es->number,
+ mins, maxs, ent.axis, ent.origin );
+
+ //offset on the Z axis if required
+ VectorMA( ent.origin, BG_FindZOffsetForBuildable( es->modelindex ), surfNormal, ent.origin );
+
+ VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all
+ VectorCopy( ent.origin, ent.lightingOrigin );
+
+ ent.hModel = cg_buildables[ es->modelindex ].models[ 0 ];
+
+ if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ {
+ sfxHandle_t prebuildSound = cgs.media.humanBuildablePrebuild;
+
+ if( team == BIT_HUMANS )
+ {
+ ent.customShader = cgs.media.humanSpawningShader;
+ prebuildSound = cgs.media.humanBuildablePrebuild;
+ }
+ else if( team == BIT_ALIENS )
+ prebuildSound = cgs.media.alienBuildablePrebuild;
+
+ trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound );
+ }
+
+ CG_BuildableAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp );
+
+ //rescale the model
+ scale = BG_FindModelScaleForBuildable( es->modelindex );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] );
+
+ ent.nonNormalizedAxes = qtrue;
+ }
+ else
+ ent.nonNormalizedAxes = qfalse;
+
+
+ //add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+
+ CrossProduct( surfNormal, refNormal, xNormal );
+ VectorNormalize( xNormal );
+ rotAngle = RAD2DEG( acos( DotProduct( surfNormal, refNormal ) ) );
+
+ //turret barrel bit
+ if( cg_buildables[ es->modelindex ].models[ 1 ] )
+ {
+ refEntity_t turretBarrel;
+ vec3_t flatAxis[ 3 ];
+
+ memset( &turretBarrel, 0, sizeof( turretBarrel ) );
+
+ turretBarrel.hModel = cg_buildables[ es->modelindex ].models[ 1 ];
+
+ CG_PositionEntityOnTag( &turretBarrel, &ent, ent.hModel, "tag_turret" );
+ VectorCopy( cent->lerpOrigin, turretBarrel.lightingOrigin );
+ AnglesToAxis( es->angles2, flatAxis );
+
+ RotatePointAroundVector( turretBarrel.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle );
+ RotatePointAroundVector( turretBarrel.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle );
+ RotatePointAroundVector( turretBarrel.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle );
+
+ turretBarrel.oldframe = ent.oldframe;
+ turretBarrel.frame = ent.frame;
+ turretBarrel.backlerp = ent.backlerp;
+
+ turretBarrel.customShader = ent.customShader;
+
+ if( scale != 1.0f )
+ {
+ VectorScale( turretBarrel.axis[ 0 ], scale, turretBarrel.axis[ 0 ] );
+ VectorScale( turretBarrel.axis[ 1 ], scale, turretBarrel.axis[ 1 ] );
+ VectorScale( turretBarrel.axis[ 2 ], scale, turretBarrel.axis[ 2 ] );
+
+ turretBarrel.nonNormalizedAxes = qtrue;
+ }
+ else
+ turretBarrel.nonNormalizedAxes = qfalse;
+
+ trap_R_AddRefEntityToScene( &turretBarrel );
+ }
+
+ //turret barrel bit
+ if( cg_buildables[ es->modelindex ].models[ 2 ] )
+ {
+ refEntity_t turretTop;
+ vec3_t flatAxis[ 3 ];
+ vec3_t swivelAngles;
+
+ memset( &turretTop, 0, sizeof( turretTop ) );
+
+ VectorCopy( es->angles2, swivelAngles );
+ swivelAngles[ PITCH ] = 0.0f;
+
+ turretTop.hModel = cg_buildables[ es->modelindex ].models[ 2 ];
+
+ CG_PositionRotatedEntityOnTag( &turretTop, &ent, ent.hModel, "tag_turret" );
+ VectorCopy( cent->lerpOrigin, turretTop.lightingOrigin );
+ AnglesToAxis( swivelAngles, flatAxis );
+
+ RotatePointAroundVector( turretTop.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle );
+ RotatePointAroundVector( turretTop.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle );
+ RotatePointAroundVector( turretTop.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle );
+
+ turretTop.oldframe = ent.oldframe;
+ turretTop.frame = ent.frame;
+ turretTop.backlerp = ent.backlerp;
+
+ turretTop.customShader = ent.customShader;
+
+ if( scale != 1.0f )
+ {
+ VectorScale( turretTop.axis[ 0 ], scale, turretTop.axis[ 0 ] );
+ VectorScale( turretTop.axis[ 1 ], scale, turretTop.axis[ 1 ] );
+ VectorScale( turretTop.axis[ 2 ], scale, turretTop.axis[ 2 ] );
+
+ turretTop.nonNormalizedAxes = qtrue;
+ }
+ else
+ turretTop.nonNormalizedAxes = qfalse;
+
+ trap_R_AddRefEntityToScene( &turretTop );
+ }
+
+ //weapon effects for turrets
+ if( es->eFlags & EF_FIRING )
+ {
+ weaponInfo_t *weapon = &cg_weapons[ es->weapon ];
+
+ if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ||
+ BG_FindProjTypeForBuildable( es->modelindex ) == WP_TESLAGEN )
+ {
+ if( weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ] ||
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ] ||
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] )
+ {
+ trap_R_AddLightToScene( cent->lerpOrigin, 300 + ( rand( ) & 31 ),
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ],
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ],
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] );
+ }
+ }
+
+ if( weapon->wim[ WPM_PRIMARY ].firingSound )
+ {
+ trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin,
+ weapon->wim[ WPM_PRIMARY ].firingSound );
+ }
+ else if( weapon->readySound )
+ trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound );
+ }
+
+ health = es->generic1 & B_HEALTH_MASK;
+ healthScale = (float)health / B_HEALTH_MASK;
+
+ if( healthScale < cent->lastBuildableHealthScale && ( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ {
+ if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time )
+ {
+ if( team == BIT_HUMANS )
+ {
+ int i = rand( ) % 4;
+ trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.humanBuildableDamage[ i ] );
+ }
+ else if( team == BIT_ALIENS )
+ trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienBuildableDamage );
+
+ cent->lastBuildableDamageSoundTime = cg.time;
+ }
+ }
+
+ cent->lastBuildableHealthScale = healthScale;
+
+ //smoke etc for damaged buildables
+ CG_BuildableParticleEffects( cent );
+}
diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c
new file mode 100644
index 0000000..68aab6c
--- /dev/null
+++ b/src/cgame/cg_consolecmds.c
@@ -0,0 +1,325 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_consolecmds.c -- text commands typed in at the local console, or
+// executed by a key binding
+
+
+#include "cg_local.h"
+
+
+
+void CG_TargetCommand_f( void )
+{
+ int targetNum;
+ char test[ 4 ];
+
+ targetNum = CG_CrosshairPlayer( );
+ if( !targetNum )
+ return;
+
+ trap_Argv( 1, test, 4 );
+ trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) );
+}
+
+
+
+/*
+=================
+CG_SizeUp_f
+
+Keybinding command
+=================
+*/
+static void CG_SizeUp_f( void )
+{
+ trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer + 10 ) ) );
+}
+
+
+/*
+=================
+CG_SizeDown_f
+
+Keybinding command
+=================
+*/
+static void CG_SizeDown_f( void )
+{
+ trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer - 10 ) ) );
+}
+
+
+/*
+=============
+CG_Viewpos_f
+
+Debugging command to print the current position
+=============
+*/
+static void CG_Viewpos_f( void )
+{
+ CG_Printf( "(%i %i %i) : %i\n", (int)cg.refdef.vieworg[ 0 ],
+ (int)cg.refdef.vieworg[ 1 ], (int)cg.refdef.vieworg[ 2 ],
+ (int)cg.refdefViewAngles[ YAW ] );
+}
+
+qboolean CG_RequestScores( void )
+{
+ if( cg.scoresRequestTime + 2000 < cg.time )
+ {
+ // the scores are more than two seconds out of data,
+ // so request new ones
+ cg.scoresRequestTime = cg.time;
+ //TA: added \n SendClientCommand doesn't call flush( )?
+ trap_SendClientCommand( "score\n" );
+
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+extern menuDef_t *menuScoreboard;
+
+static void CG_scrollScoresDown_f( void )
+{
+ if( menuScoreboard && cg.scoreBoardShowing )
+ {
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qtrue );
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qtrue );
+ }
+}
+
+
+static void CG_scrollScoresUp_f( void )
+{
+ if( menuScoreboard && cg.scoreBoardShowing )
+ {
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qfalse );
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qfalse );
+ }
+}
+
+static void CG_ScoresDown_f( void )
+{
+ if( !cg.showScores )
+ {
+ Menu_SetFeederSelection( menuScoreboard, FEEDER_ALIENTEAM_LIST, 0, NULL );
+ Menu_SetFeederSelection( menuScoreboard, FEEDER_HUMANTEAM_LIST, 0, NULL );
+ }
+
+ if( CG_RequestScores( ) )
+ {
+ // leave the current scores up if they were already
+ // displayed, but if this is the first hit, clear them out
+ if( !cg.showScores )
+ {
+ if( cg_debugRandom.integer )
+ CG_Printf( "CG_ScoresDown_f: scores out of date\n" );
+
+ cg.showScores = qtrue;
+ cg.numScores = 0;
+ }
+ }
+ else
+ {
+ // show the cached contents even if they just pressed if it
+ // is within two seconds
+ cg.showScores = qtrue;
+ }
+}
+
+static void CG_ScoresUp_f( void )
+{
+ if( cg.showScores )
+ {
+ cg.showScores = qfalse;
+ cg.scoreFadeTime = cg.time;
+ }
+}
+
+static void CG_TellTarget_f( void )
+{
+ int clientNum;
+ char command[ 128 ];
+ char message[ 128 ];
+
+ clientNum = CG_CrosshairPlayer( );
+ if( clientNum == -1 )
+ return;
+
+ trap_Args( message, 128 );
+ Com_sprintf( command, 128, "tell %i %s", clientNum, message );
+ trap_SendClientCommand( command );
+}
+
+static void CG_TellAttacker_f( void )
+{
+ int clientNum;
+ char command[ 128 ];
+ char message[ 128 ];
+
+ clientNum = CG_LastAttacker( );
+ if( clientNum == -1 )
+ return;
+
+ trap_Args( message, 128 );
+ Com_sprintf( command, 128, "tell %i %s", clientNum, message );
+ trap_SendClientCommand( command );
+}
+
+typedef struct
+{
+ char *cmd;
+ void (*function)( void );
+} consoleCommand_t;
+
+static consoleCommand_t commands[ ] =
+{
+ { "testgun", CG_TestGun_f },
+ { "testmodel", CG_TestModel_f },
+ { "nextframe", CG_TestModelNextFrame_f },
+ { "prevframe", CG_TestModelPrevFrame_f },
+ { "nextskin", CG_TestModelNextSkin_f },
+ { "prevskin", CG_TestModelPrevSkin_f },
+ { "viewpos", CG_Viewpos_f },
+ { "+scores", CG_ScoresDown_f },
+ { "-scores", CG_ScoresUp_f },
+ { "scoresUp", CG_scrollScoresUp_f },
+ { "scoresDown", CG_scrollScoresDown_f },
+ { "sizeup", CG_SizeUp_f },
+ { "sizedown", CG_SizeDown_f },
+ { "weapnext", CG_NextWeapon_f },
+ { "weapprev", CG_PrevWeapon_f },
+ { "weapon", CG_Weapon_f },
+ { "tell_target", CG_TellTarget_f },
+ { "tell_attacker", CG_TellAttacker_f },
+ { "tcmd", CG_TargetCommand_f },
+ { "testPS", CG_TestPS_f },
+ { "destroyTestPS", CG_DestroyTestPS_f },
+ { "testTS", CG_TestTS_f },
+ { "destroyTestTS", CG_DestroyTestTS_f },
+};
+
+
+/*
+=================
+CG_ConsoleCommand
+
+The string has been tokenized and can be retrieved with
+Cmd_Argc() / Cmd_Argv()
+=================
+*/
+qboolean CG_ConsoleCommand( void )
+{
+ const char *cmd;
+ const char *arg1;
+ int i;
+
+ cmd = CG_Argv( 0 );
+
+ //TA: ugly hacky special case
+ if( !Q_stricmp( cmd, "ui_menu" ) )
+ {
+ arg1 = CG_Argv( 1 );
+ trap_SendConsoleCommand( va( "menu %s\n", arg1 ) );
+ return qtrue;
+ }
+
+ for( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); i++ )
+ {
+ if( !Q_stricmp( cmd, commands[ i ].cmd ) )
+ {
+ commands[ i ].function( );
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+
+/*
+=================
+CG_InitConsoleCommands
+
+Let the client system know about all of our commands
+so it can perform tab completion
+=================
+*/
+void CG_InitConsoleCommands( void )
+{
+ int i;
+
+ for( i = 0 ; i < sizeof( commands ) / sizeof( commands[ 0 ] ) ; i++ )
+ trap_AddCommand( commands[ i ].cmd );
+
+ //
+ // the game server will interpret these commands, which will be automatically
+ // forwarded to the server after they are not recognized locally
+ //
+ trap_AddCommand( "kill" );
+ trap_AddCommand( "say" );
+ trap_AddCommand( "say_team" );
+ trap_AddCommand( "tell" );
+ trap_AddCommand( "vsay" );
+ trap_AddCommand( "vsay_team" );
+ trap_AddCommand( "vtell" );
+ trap_AddCommand( "vtaunt" );
+ trap_AddCommand( "vosay" );
+ trap_AddCommand( "vosay_team" );
+ trap_AddCommand( "votell" );
+ trap_AddCommand( "give" );
+ trap_AddCommand( "god" );
+ trap_AddCommand( "notarget" );
+ trap_AddCommand( "noclip" );
+ trap_AddCommand( "team" );
+ trap_AddCommand( "follow" );
+ trap_AddCommand( "levelshot" );
+ trap_AddCommand( "addbot" );
+ trap_AddCommand( "setviewpos" );
+ trap_AddCommand( "callvote" );
+ trap_AddCommand( "vote" );
+ trap_AddCommand( "callteamvote" );
+ trap_AddCommand( "teamvote" );
+ trap_AddCommand( "stats" );
+ trap_AddCommand( "teamtask" );
+ trap_AddCommand( "class" );
+ trap_AddCommand( "build" );
+ trap_AddCommand( "buy" );
+ trap_AddCommand( "sell" );
+ trap_AddCommand( "reload" );
+ trap_AddCommand( "itemact" );
+ trap_AddCommand( "itemdeact" );
+ trap_AddCommand( "itemtoggle" );
+ trap_AddCommand( "destroy" );
+ trap_AddCommand( "deconstruct" );
+ trap_AddCommand( "menu" );
+ trap_AddCommand( "ui_menu" );
+ trap_AddCommand( "mapRotation" );
+ trap_AddCommand( "stopMapRotation" );
+ trap_AddCommand( "advanceMapRotation" );
+ trap_AddCommand( "alienWin" );
+ trap_AddCommand( "humanWin" );
+}
diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c
new file mode 100644
index 0000000..1a3ecae
--- /dev/null
+++ b/src/cgame/cg_draw.c
@@ -0,0 +1,3515 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_draw.c -- draw all of the graphical elements during
+// active (after loading) gameplay
+
+
+#include "cg_local.h"
+#include "../ui/ui_shared.h"
+
+// used for scoreboard
+extern displayContextDef_t cgDC;
+menuDef_t *menuScoreboard = NULL;
+
+int drawTeamOverlayModificationCount = -1;
+
+int sortedTeamPlayers[ TEAM_MAXOVERLAY ];
+int numSortedTeamPlayers;
+
+//TA UI
+int CG_Text_Width( const char *text, float scale, int limit )
+{
+ int count,len;
+ float out;
+ glyphInfo_t *glyph;
+ float useScale;
+// FIXME: see ui_main.c, same problem
+// const unsigned char *s = text;
+ const char *s = text;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ out = 0;
+
+ if( text )
+ {
+ len = strlen( text );
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+ while( s && *s && count < len )
+ {
+ if( Q_IsColorString( s ) )
+ {
+ s += 2;
+ continue;
+ }
+ else
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+ out += glyph->xSkip;
+ s++;
+ count++;
+ }
+ }
+ }
+
+ return out * useScale;
+}
+
+int CG_Text_Height( const char *text, float scale, int limit )
+{
+ int len, count;
+ float max;
+ glyphInfo_t *glyph;
+ float useScale;
+// TTimo: FIXME
+// const unsigned char *s = text;
+ const char *s = text;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ max = 0;
+
+ if( text )
+ {
+ len = strlen( text );
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+ while( s && *s && count < len )
+ {
+ if( Q_IsColorString( s ) )
+ {
+ s += 2;
+ continue;
+ }
+ else
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+ if( max < glyph->height )
+ max = glyph->height;
+
+ s++;
+ count++;
+ }
+ }
+ }
+
+ return max * useScale;
+}
+
+void CG_Text_PaintChar( float x, float y, float width, float height, float scale,
+ float s, float t, float s2, float t2, qhandle_t hShader )
+{
+ float w, h;
+ w = width * scale;
+ h = height * scale;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader );
+}
+
+void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text,
+ float adjust, int limit, int style )
+{
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+ float useScale;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ if( text )
+ {
+// TTimo: FIXME
+// const unsigned char *s = text;
+ const char *s = text;
+
+ trap_R_SetColor( color );
+ memcpy( &newColor[ 0 ], &color[ 0 ], sizeof( vec4_t ) );
+ len = strlen( text );
+
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+ while( s && *s && count < len )
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+
+ if( Q_IsColorString( s ) )
+ {
+ memcpy( newColor, g_color_table[ ColorIndex( *( s + 1 ) ) ], sizeof( newColor ) );
+ newColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ }
+ else
+ {
+ float yadj = useScale * glyph->top;
+ if( style == ITEM_TEXTSTYLE_SHADOWED ||
+ style == ITEM_TEXTSTYLE_SHADOWEDMORE )
+ {
+ int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;
+ colorBlack[ 3 ] = newColor[ 3 ];
+ trap_R_SetColor( colorBlack );
+ CG_Text_PaintChar( x + ofs, y - yadj + ofs,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ colorBlack[ 3 ] = 1.0;
+ trap_R_SetColor( newColor );
+ }
+ else if( style == ITEM_TEXTSTYLE_NEON )
+ {
+ vec4_t glow, outer, inner, white;
+
+ glow[ 0 ] = newColor[ 0 ] * 0.5;
+ glow[ 1 ] = newColor[ 1 ] * 0.5;
+ glow[ 2 ] = newColor[ 2 ] * 0.5;
+ glow[ 3 ] = newColor[ 3 ] * 0.2;
+
+ outer[ 0 ] = newColor[ 0 ];
+ outer[ 1 ] = newColor[ 1 ];
+ outer[ 2 ] = newColor[ 2 ];
+ outer[ 3 ] = newColor[ 3 ];
+
+ inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5;
+ inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5;
+ inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5;
+ inner[ 3 ] = newColor[ 3 ];
+
+ white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f;
+
+ trap_R_SetColor( glow );
+ CG_Text_PaintChar( x - 3, y - yadj - 3,
+ glyph->imageWidth + 6,
+ glyph->imageHeight + 6,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( outer );
+ CG_Text_PaintChar( x - 1, y - yadj - 1,
+ glyph->imageWidth + 2,
+ glyph->imageHeight + 2,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( inner );
+ CG_Text_PaintChar( x - 0.5, y - yadj - 0.5,
+ glyph->imageWidth + 1,
+ glyph->imageHeight + 1,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( white );
+ }
+
+
+ CG_Text_PaintChar( x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ x += ( glyph->xSkip * useScale ) + adjust;
+ s++;
+ count++;
+ }
+ }
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+/*
+==============
+CG_DrawFieldPadded
+
+Draws large numbers for status bar and powerups
+==============
+*/
+static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int value )
+{
+ char num[ 16 ], *ptr;
+ int l, orgL;
+ int frame;
+ int charWidth, charHeight;
+
+ if( !( charWidth = cw ) )
+ charWidth = CHAR_WIDTH;
+
+ if( !( charHeight = ch ) )
+ charWidth = CHAR_HEIGHT;
+
+ if( width < 1 )
+ return;
+
+ // draw number string
+ if( width > 4 )
+ width = 4;
+
+ switch( width )
+ {
+ case 1:
+ value = value > 9 ? 9 : value;
+ value = value < 0 ? 0 : value;
+ break;
+ case 2:
+ value = value > 99 ? 99 : value;
+ value = value < -9 ? -9 : value;
+ break;
+ case 3:
+ value = value > 999 ? 999 : value;
+ value = value < -99 ? -99 : value;
+ break;
+ case 4:
+ value = value > 9999 ? 9999 : value;
+ value = value < -999 ? -999 : value;
+ break;
+ }
+
+ Com_sprintf( num, sizeof( num ), "%d", value );
+ l = strlen( num );
+
+ if( l > width )
+ l = width;
+
+ orgL = l;
+
+ x += 2;
+
+ ptr = num;
+ while( *ptr && l )
+ {
+ if( width > orgL )
+ {
+ CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ 0 ] );
+ width--;
+ x += charWidth;
+ continue;
+ }
+
+ if( *ptr == '-' )
+ frame = STAT_MINUS;
+ else
+ frame = *ptr - '0';
+
+ CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] );
+ x += charWidth;
+ ptr++;
+ l--;
+ }
+}
+
+/*
+==============
+CG_DrawField
+
+Draws large numbers for status bar and powerups
+==============
+*/
+void CG_DrawField( float x, float y, int width, float cw, float ch, int value )
+{
+ char num[ 16 ], *ptr;
+ int l;
+ int frame;
+ float charWidth, charHeight;
+
+ if( !( charWidth = cw ) )
+ charWidth = CHAR_WIDTH;
+
+ if( !( charHeight = ch ) )
+ charWidth = CHAR_HEIGHT;
+
+ if( width < 1 )
+ return;
+
+ // draw number string
+ if( width > 4 )
+ width = 4;
+
+ switch( width )
+ {
+ case 1:
+ value = value > 9 ? 9 : value;
+ value = value < 0 ? 0 : value;
+ break;
+ case 2:
+ value = value > 99 ? 99 : value;
+ value = value < -9 ? -9 : value;
+ break;
+ case 3:
+ value = value > 999 ? 999 : value;
+ value = value < -99 ? -99 : value;
+ break;
+ case 4:
+ value = value > 9999 ? 9999 : value;
+ value = value < -999 ? -999 : value;
+ break;
+ }
+
+ Com_sprintf( num, sizeof( num ), "%d", value );
+ l = strlen( num );
+
+ if( l > width )
+ l = width;
+
+ x += 2 + charWidth * ( width - l );
+
+ ptr = num;
+ while( *ptr && l )
+ {
+ if( *ptr == '-' )
+ frame = STAT_MINUS;
+ else
+ frame = *ptr -'0';
+
+ CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] );
+ x += charWidth;
+ ptr++;
+ l--;
+ }
+}
+
+static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special, float progress )
+{
+ float rimWidth = rect->h / 20.0f;
+ float doneWidth, leftWidth;
+ float tx, ty, tw, th;
+ char textBuffer[ 8 ];
+
+ if( rimWidth < 0.6f )
+ rimWidth = 0.6f;
+
+ if( special >= 0.0f )
+ rimWidth = special;
+
+ if( progress < 0.0f )
+ progress = 0.0f;
+ else if( progress > 1.0f )
+ progress = 1.0f;
+
+ doneWidth = ( rect->w - 2 * rimWidth ) * progress;
+ leftWidth = ( rect->w - 2 * rimWidth ) - doneWidth;
+
+ trap_R_SetColor( color );
+
+ //draw rim and bar
+ if( align == ITEM_ALIGN_RIGHT )
+ {
+ CG_DrawPic( rect->x, rect->y, rimWidth, rect->h, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rimWidth, rect->y,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rimWidth, rect->y + rect->h - rimWidth,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rimWidth + leftWidth, rect->y,
+ rimWidth + doneWidth, rect->h, cgs.media.whiteShader );
+ }
+ else
+ {
+ CG_DrawPic( rect->x, rect->y, rimWidth + doneWidth, rect->h, cgs.media.whiteShader );
+ CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y + rect->h - rimWidth,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rect->w - rimWidth, rect->y, rimWidth, rect->h, cgs.media.whiteShader );
+ }
+
+ trap_R_SetColor( NULL );
+
+ //draw text
+ if( scale > 0.0 )
+ {
+ Com_sprintf( textBuffer, sizeof( textBuffer ), "%d%%", (int)( progress * 100 ) );
+ tw = CG_Text_Width( textBuffer, scale, 0 );
+ th = scale * 40.0f;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x + ( rect->w / 10.0f );
+ ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f );
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - ( rect->w / 10.0f ) - tw;
+ ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f );
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f );
+ ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f );
+ break;
+
+ default:
+ tx = ty = 0.0f;
+ }
+
+ CG_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle );
+ }
+}
+
+//=============== TA: was cg_newdraw.c
+
+#define NO_CREDITS_TIME 2000
+
+static void CG_DrawPlayerCreditsValue( rectDef_t *rect, vec4_t color, qboolean padding )
+{
+ int value;
+ playerState_t *ps;
+ centity_t *cent;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ //if the build timer pie is showing don't show this
+ if( ( cent->currentState.weapon == WP_ABUILD ||
+ cent->currentState.weapon == WP_ABUILD2 ) && ps->stats[ STAT_MISC ] )
+ return;
+
+ value = ps->persistant[ PERS_CREDIT ];
+ if( value > -1 )
+ {
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ !CG_AtHighestClass( ) )
+ {
+ if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME )
+ {
+ if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 )
+ color[ 3 ] = 0.0f;
+ }
+ }
+
+ trap_R_SetColor( color );
+
+ if( padding )
+ CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ else
+ CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value );
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+static void CG_DrawPlayerBankValue( rectDef_t *rect, vec4_t color, qboolean padding )
+{
+ int value;
+ playerState_t *ps;
+
+ ps = &cg.snap->ps;
+
+ value = ps->persistant[ PERS_BANK ];
+ if( value > -1 )
+ {
+ trap_R_SetColor( color );
+
+ if( padding )
+ CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ else
+ CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value );
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+#define HH_MIN_ALPHA 0.2f
+#define HH_MAX_ALPHA 0.8f
+#define HH_ALPHA_DIFF (HH_MAX_ALPHA-HH_MIN_ALPHA)
+
+#define AH_MIN_ALPHA 0.2f
+#define AH_MAX_ALPHA 0.8f
+#define AH_ALPHA_DIFF (AH_MAX_ALPHA-AH_MIN_ALPHA)
+
+/*
+==============
+CG_DrawPlayerStamina1
+==============
+*/
+static void CG_DrawPlayerStamina1( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f;
+ float progress;
+
+ stamina -= ( 2 * (int)maxStaminaBy3 );
+ progress = stamina / maxStaminaBy3;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStamina2
+==============
+*/
+static void CG_DrawPlayerStamina2( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f;
+ float progress;
+
+ stamina -= (int)maxStaminaBy3;
+ progress = stamina / maxStaminaBy3;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStamina3
+==============
+*/
+static void CG_DrawPlayerStamina3( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f;
+ float progress;
+
+ progress = stamina / maxStaminaBy3;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStamina4
+==============
+*/
+static void CG_DrawPlayerStamina4( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float progress;
+
+ stamina += (float)MAX_STAMINA;
+ progress = stamina / (float)MAX_STAMINA;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStaminaBolt
+==============
+*/
+static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+
+ if( stamina < 0 )
+ color[ 3 ] = HH_MIN_ALPHA;
+ else
+ color[ 3 ] = HH_MAX_ALPHA;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerClipsRing
+==============
+*/
+static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ centity_t *cent;
+ float buildTime = ps->stats[ STAT_MISC ];
+ float progress;
+ float maxDelay;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon );
+
+ if( buildTime > maxDelay )
+ buildTime = maxDelay;
+
+ progress = ( maxDelay - buildTime ) / maxDelay;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+ break;
+
+ default:
+ if( ps->weaponstate == WEAPON_RELOADING )
+ {
+ maxDelay = (float)BG_FindReloadTimeForWeapon( cent->currentState.weapon );
+ progress = ( maxDelay - (float)ps->weaponTime ) / maxDelay;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+ }
+ break;
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerBuildTimerRing
+==============
+*/
+static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ centity_t *cent;
+ float buildTime = ps->stats[ STAT_MISC ];
+ float progress;
+ float maxDelay;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+
+ maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon );
+
+ if( buildTime > maxDelay )
+ buildTime = maxDelay;
+
+ progress = ( maxDelay - buildTime ) / maxDelay;
+
+ color[ 3 ] = AH_MIN_ALPHA + ( progress * AH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerBoosted
+==============
+*/
+static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED;
+
+ if( boosted )
+ color[ 3 ] = AH_MAX_ALPHA;
+ else
+ color[ 3 ] = AH_MIN_ALPHA;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerBoosterBolt
+==============
+*/
+static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED;
+ vec4_t localColor;
+
+ Vector4Copy( color, localColor );
+
+ if( boosted )
+ {
+ if( ps->stats[ STAT_BOOSTTIME ] > BOOST_TIME - 3000 )
+ {
+ qboolean flash = ( ps->stats[ STAT_BOOSTTIME ] / 500 ) % 2;
+
+ if( flash )
+ localColor[ 3 ] = 1.0f;
+ }
+ }
+
+ trap_R_SetColor( localColor );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerPoisonBarbs
+==============
+*/
+static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ int x = rect->x;
+ int y = rect->y;
+ int width = rect->w;
+ int height = rect->h;
+ qboolean vertical;
+ int iconsize, numBarbs, i;
+
+ numBarbs = ps->ammo;
+
+ if( height > width )
+ {
+ vertical = qtrue;
+ iconsize = width;
+ }
+ else if( height <= width )
+ {
+ vertical = qfalse;
+ iconsize = height;
+ }
+
+ if( color[ 3 ] != 0.0 )
+ trap_R_SetColor( color );
+
+ for( i = 0; i < numBarbs; i ++ )
+ {
+ if( vertical )
+ y += iconsize;
+ else
+ x += iconsize;
+
+ CG_DrawPic( x, y, iconsize, iconsize, shader );
+ }
+
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerWallclimbing
+==============
+*/
+static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ qboolean ww = ps->stats[ STAT_STATE ] & SS_WALLCLIMBING;
+
+ if( ww )
+ color[ 3 ] = AH_MAX_ALPHA;
+ else
+ color[ 3 ] = AH_MIN_ALPHA;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+static void CG_DrawPlayerStamina( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ playerState_t *ps = &cg.snap->ps;
+ int stamina = ps->stats[ STAT_STAMINA ];
+ float progress = ( (float)stamina + (float)MAX_STAMINA ) / ( (float)MAX_STAMINA * 2.0f );
+
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, progress );
+}
+
+static void CG_DrawPlayerAmmoValue( rectDef_t *rect, vec4_t color )
+{
+ int value;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ if( cent->currentState.weapon )
+ {
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ //percentage of BP remaining
+ value = cgs.alienBuildPoints;
+ break;
+
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ //percentage of BP remaining
+ value = cgs.humanBuildPoints;
+ break;
+
+ default:
+ value = ps->ammo;
+ break;
+ }
+
+ if( value > 999 )
+ value = 999;
+
+ if( value > -1 )
+ {
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ trap_R_SetColor( NULL );
+ }
+ }
+}
+
+
+/*
+==============
+CG_DrawAlienSense
+==============
+*/
+static void CG_DrawAlienSense( rectDef_t *rect )
+{
+ if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_PCLASS ], SCA_ALIENSENSE ) )
+ CG_AlienSense( rect );
+}
+
+
+/*
+==============
+CG_DrawHumanScanner
+==============
+*/
+static void CG_DrawHumanScanner( rectDef_t *rect, qhandle_t shader, vec4_t color )
+{
+ if( BG_InventoryContainsUpgrade( UP_HELMET, cg.snap->ps.stats ) )
+ CG_Scanner( rect, shader, color );
+}
+
+
+/*
+==============
+CG_DrawUsableBuildable
+==============
+*/
+static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t color )
+{
+ vec3_t view, point;
+ trace_t trace;
+ entityState_t *es;
+
+ AngleVectors( cg.refdefViewAngles, view, NULL, NULL );
+ VectorMA( cg.refdef.vieworg, 64, view, point );
+ CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL,
+ point, cg.predictedPlayerState.clientNum, MASK_SHOT );
+
+ es = &cg_entities[ trace.entityNum ].currentState;
+
+ if( es->eType == ET_BUILDABLE && BG_FindUsableForBuildable( es->modelindex ) &&
+ cg.predictedPlayerState.stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) )
+ {
+ //hack to prevent showing the usable buildable when you aren't carrying an energy weapon
+ if( ( es->modelindex == BA_H_REACTOR || es->modelindex == BA_H_REPEATER ) &&
+ ( !BG_FindUsesEnergyForWeapon( cg.snap->ps.weapon ) ||
+ BG_FindInfinteAmmoForWeapon( cg.snap->ps.weapon ) ) )
+ return;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+ }
+}
+
+
+#define BUILD_DELAY_TIME 2000
+
+static void CG_DrawPlayerBuildTimer( rectDef_t *rect, vec4_t color )
+{
+ float progress;
+ int index;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ if( cent->currentState.weapon )
+ {
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_BASE_DELAY;
+ break;
+
+ case WP_ABUILD2:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_ADV_DELAY;
+ break;
+
+ case WP_HBUILD:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD_DELAY;
+ break;
+
+ case WP_HBUILD2:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD2_DELAY;
+ break;
+
+ default:
+ return;
+ break;
+ }
+
+ if( !ps->stats[ STAT_MISC ] )
+ return;
+
+ index = (int)( progress * 8.0f );
+
+ if( index > 7 )
+ index = 7;
+ else if( index < 0 )
+ index = 0;
+
+ if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME )
+ {
+ if( ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 )
+ {
+ color[ 0 ] = 1.0f;
+ color[ 1 ] = color[ 2 ] = 0.0f;
+ color[ 3 ] = 1.0f;
+ }
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h,
+ cgs.media.buildWeaponTimerPie[ index ] );
+ trap_R_SetColor( NULL );
+ }
+}
+
+static void CG_DrawPlayerClipsValue( rectDef_t *rect, vec4_t color )
+{
+ int value;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ if( cent->currentState.weapon )
+ {
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ break;
+
+ default:
+ value = ps->clips;
+
+ if( value > -1 )
+ {
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ trap_R_SetColor( NULL );
+ }
+ break;
+ }
+ }
+}
+
+static void CG_DrawPlayerHealthValue( rectDef_t *rect, vec4_t color )
+{
+ playerState_t *ps;
+ int value;
+
+ ps = &cg.snap->ps;
+
+ value = ps->stats[ STAT_HEALTH ];
+
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ trap_R_SetColor( NULL );
+}
+
+static void CG_DrawPlayerHealthBar( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ playerState_t *ps;
+ float total;
+
+ ps = &cg.snap->ps;
+
+ total = ( (float)ps->stats[ STAT_HEALTH ] / (float)ps->stats[ STAT_MAX_HEALTH ] );
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total );
+}
+
+/*
+==============
+CG_DrawPlayerHealthCross
+==============
+*/
+static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ int health = ps->stats[ STAT_HEALTH ];
+
+ if( health < 10 )
+ {
+ color[ 0 ] = 1.0f;
+ color[ 1 ] = color[ 2 ] = 0.0f;
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, const char *s, float fraction )
+{
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+ float tx, tw = CG_Text_Width( s, scale, 0 );
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = 0.0f;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->w - tw;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = ( rect->w / 2.0f ) - ( tw / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ if( fraction < 1.0f )
+ CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, white,
+ s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+ else
+ CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, color,
+ s, 0, 0, ITEM_TEXTSTYLE_NEON );
+}
+
+static void CG_DrawMediaProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.mediaFraction );
+}
+
+static void CG_DrawMediaProgressLabel( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align )
+{
+ CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Map and Textures", cg.mediaFraction );
+}
+
+static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.buildablesFraction );
+}
+
+static void CG_DrawBuildablesProgressLabel( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align )
+{
+ CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Buildable Models", cg.buildablesFraction );
+}
+
+static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.charModelFraction );
+}
+
+static void CG_DrawCharModelProgressLabel( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align )
+{
+ CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Character Models", cg.charModelFraction );
+}
+
+static void CG_DrawOverallProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ float total;
+
+ total = ( cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction ) / 3.0f;
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total );
+}
+
+static void CG_DrawLevelShot( rectDef_t *rect )
+{
+ const char *s;
+ const char *info;
+ qhandle_t levelshot;
+ qhandle_t detail;
+
+ info = CG_ConfigString( CS_SERVERINFO );
+ s = Info_ValueForKey( info, "mapname" );
+ levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) );
+
+ if( !levelshot )
+ levelshot = trap_R_RegisterShaderNoMip( "gfx/2d/load_screen" );
+
+ trap_R_SetColor( NULL );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, levelshot );
+
+ // blend a detail texture over it
+ detail = trap_R_RegisterShader( "gfx/misc/detail" );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, detail );
+}
+
+static void CG_DrawLoadingString( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle, const char *s )
+{
+ float tw, th, tx;
+ int pos, i;
+ char buffer[ 1024 ];
+ char *end;
+
+ if( !s[ 0 ] )
+ return;
+
+ strcpy( buffer, s );
+ tw = CG_Text_Width( s, scale, 0 );
+ th = scale * 40.0f;
+
+ pos = i = 0;
+
+ while( pos < strlen( s ) )
+ {
+ strcpy( buffer, &s[ pos ] );
+ tw = CG_Text_Width( buffer, scale, 0 );
+
+ while( tw > rect->w )
+ {
+ end = strrchr( buffer, ' ' );
+
+ if( end == NULL )
+ break;
+
+ *end = '\0';
+ tw = CG_Text_Width( buffer, scale, 0 );
+ }
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - tw;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ CG_Text_Paint( tx + text_x, rect->y + text_y + i * ( th + 3 ), scale, color,
+ buffer, 0, 0, textStyle );
+
+ pos += strlen( buffer ) + 1;
+ i++;
+ }
+}
+
+static void CG_DrawLevelName( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ const char *s;
+
+ s = CG_ConfigString( CS_MESSAGE );
+
+ CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s );
+}
+
+static void CG_DrawMOTD( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ const char *s;
+
+ s = CG_ConfigString( CS_MOTD );
+
+ CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s );
+}
+
+static void CG_DrawHostname( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ char buffer[ 1024 ];
+ const char *info;
+
+ info = CG_ConfigString( CS_SERVERINFO );
+
+ Q_strncpyz( buffer, Info_ValueForKey( info, "sv_hostname" ), 1024 );
+ Q_CleanStr( buffer );
+
+ CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, buffer );
+}
+
+/*
+==============
+CG_DrawDemoPlayback
+==============
+*/
+static void CG_DrawDemoPlayback( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ if( !cg_drawDemoState.integer )
+ return;
+
+ if( trap_GetDemoState( ) != DS_PLAYBACK )
+ return;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawDemoRecording
+==============
+*/
+static void CG_DrawDemoRecording( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ if( !cg_drawDemoState.integer )
+ return;
+
+ if( trap_GetDemoState( ) != DS_RECORDING )
+ return;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+======================
+CG_UpdateMediaFraction
+
+======================
+*/
+void CG_UpdateMediaFraction( float newFract )
+{
+ cg.mediaFraction = newFract;
+
+ trap_UpdateScreen( );
+}
+
+/*
+====================
+CG_DrawLoadingScreen
+
+Draw all the status / pacifier stuff during level loading
+====================
+*/
+void CG_DrawLoadingScreen( void )
+{
+ Menu_Paint( Menus_FindByName( "Loading" ), qtrue );
+}
+
+float CG_GetValue( int ownerDraw )
+{
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ switch( ownerDraw )
+ {
+ case CG_PLAYER_AMMO_VALUE:
+ if( cent->currentState.weapon )
+ {
+ return ps->ammo;
+ }
+ break;
+ case CG_PLAYER_CLIPS_VALUE:
+ if( cent->currentState.weapon )
+ {
+ return ps->clips;
+ }
+ break;
+ case CG_PLAYER_HEALTH:
+ return ps->stats[ STAT_HEALTH ];
+ break;
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+const char *CG_GetKillerText( )
+{
+ const char *s = "";
+ if( cg.killerName[ 0 ] )
+ s = va( "Fragged by %s", cg.killerName );
+
+ return s;
+}
+
+
+static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color,
+ qhandle_t shader, int textStyle )
+{
+ // fragged by ... line
+ if( cg.killerName[ 0 ] )
+ {
+ int x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - CG_Text_Width( CG_GetKillerText( ), scale, 0 ) / 2,
+ rect->y + rect->h, scale, color, CG_GetKillerText( ), 0, 0, textStyle );
+ }
+}
+
+
+static void CG_Text_Paint_Limit( float *maxX, float x, float y, float scale,
+ vec4_t color, const char* text, float adjust, int limit )
+{
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+
+ if( text )
+ {
+// TTimo: FIXME
+// const unsigned char *s = text; // bk001206 - unsigned
+ const char *s = text;
+ float max = *maxX;
+ float useScale;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ trap_R_SetColor( color );
+ len = strlen( text );
+
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+
+ while( s && *s && count < len )
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+
+ if( Q_IsColorString( s ) )
+ {
+ memcpy( newColor, g_color_table[ ColorIndex( *(s+1) ) ], sizeof( newColor ) );
+ newColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ }
+ else
+ {
+ float yadj = useScale * glyph->top;
+
+ if( CG_Text_Width( s, useScale, 1 ) + x > max )
+ {
+ *maxX = 0;
+ break;
+ }
+
+ CG_Text_PaintChar( x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+ x += ( glyph->xSkip * useScale ) + adjust;
+ *maxX = x;
+ count++;
+ s++;
+ }
+ }
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader )
+{
+ if( cg.spectatorLen )
+ {
+ float maxX;
+
+ if( cg.spectatorWidth == -1 )
+ {
+ cg.spectatorWidth = 0;
+ cg.spectatorPaintX = rect->x + 1;
+ cg.spectatorPaintX2 = -1;
+ }
+
+ if( cg.spectatorOffset > cg.spectatorLen )
+ {
+ cg.spectatorOffset = 0;
+ cg.spectatorPaintX = rect->x + 1;
+ cg.spectatorPaintX2 = -1;
+ }
+
+ if( cg.time > cg.spectatorTime )
+ {
+ cg.spectatorTime = cg.time + 10;
+
+ if( cg.spectatorPaintX <= rect->x + 2 )
+ {
+ if( cg.spectatorOffset < cg.spectatorLen )
+ {
+ //TA: skip colour directives
+ if( Q_IsColorString( &cg.spectatorList[ cg.spectatorOffset ] ) )
+ cg.spectatorOffset += 2;
+ else
+ {
+ cg.spectatorPaintX += CG_Text_Width( &cg.spectatorList[ cg.spectatorOffset ], scale, 1 ) - 1;
+ cg.spectatorOffset++;
+ }
+ }
+ else
+ {
+ cg.spectatorOffset = 0;
+
+ if( cg.spectatorPaintX2 >= 0 )
+ cg.spectatorPaintX = cg.spectatorPaintX2;
+ else
+ cg.spectatorPaintX = rect->x + rect->w - 2;
+
+ cg.spectatorPaintX2 = -1;
+ }
+ }
+ else
+ {
+ cg.spectatorPaintX--;
+
+ if( cg.spectatorPaintX2 >= 0 )
+ cg.spectatorPaintX2--;
+ }
+ }
+
+ maxX = rect->x + rect->w - 2;
+
+ CG_Text_Paint_Limit( &maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color,
+ &cg.spectatorList[ cg.spectatorOffset ], 0, 0 );
+
+ if( cg.spectatorPaintX2 >= 0 )
+ {
+ float maxX2 = rect->x + rect->w - 2;
+ CG_Text_Paint_Limit( &maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale,
+ color, cg.spectatorList, 0, cg.spectatorOffset );
+ }
+
+ if( cg.spectatorOffset && maxX > 0 )
+ {
+ // if we have an offset ( we are skipping the first part of the string ) and we fit the string
+ if( cg.spectatorPaintX2 == -1 )
+ cg.spectatorPaintX2 = rect->x + rect->w - 2;
+ }
+ else
+ cg.spectatorPaintX2 = -1;
+ }
+}
+
+/*
+==================
+CG_DrawStageReport
+==================
+*/
+static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int tx, w, kills;
+
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_NONE && !cg.intermissionStarted )
+ return;
+
+ if( cg.intermissionStarted )
+ {
+ Com_sprintf( s, MAX_TOKEN_CHARS,
+ "Stage %d" //PH34R MY MAD-LEET CODING SKILLZ
+ " "
+ "Stage %d",
+ cgs.alienStage + 1, cgs.humanStage + 1 );
+ }
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ kills = cgs.alienNextStageThreshold - cgs.alienKills;
+
+ if( cgs.alienNextStageThreshold < 0 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.alienStage + 1 );
+ else if( kills == 1 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage",
+ cgs.alienStage + 1, kills );
+ else
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage",
+ cgs.alienStage + 1, kills );
+ }
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ kills = cgs.humanNextStageThreshold - cgs.humanKills;
+
+ if( cgs.humanNextStageThreshold < 0 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.humanStage + 1 );
+ else if( kills == 1 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage",
+ cgs.humanStage + 1, kills );
+ else
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage",
+ cgs.humanStage + 1, kills );
+ }
+
+ w = CG_Text_Width( s, scale, 0 );
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle );
+}
+
+/*
+==================
+CG_DrawFPS
+==================
+*/
+//TA: personally i think this should be longer - it should really be a cvar
+#define FPS_FRAMES 20
+#define FPS_STRING "fps"
+static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle,
+ qboolean scalableText )
+{
+ char *s;
+ int tx, w, totalWidth, strLength;
+ static int previousTimes[ FPS_FRAMES ];
+ static int index;
+ int i, total;
+ int fps;
+ static int previous;
+ int t, frameTime;
+
+ if( !cg_drawFPS.integer )
+ return;
+
+ // don't use serverTime, because that will be drifting to
+ // correct for internet lag changes, timescales, timedemos, etc
+ t = trap_Milliseconds( );
+ frameTime = t - previous;
+ previous = t;
+
+ previousTimes[ index % FPS_FRAMES ] = frameTime;
+ index++;
+
+ if( index > FPS_FRAMES )
+ {
+ // average multiple frames together to smooth changes out a bit
+ total = 0;
+
+ for( i = 0 ; i < FPS_FRAMES ; i++ )
+ total += previousTimes[ i ];
+
+ if( !total )
+ total = 1;
+
+ fps = 1000 * FPS_FRAMES / total;
+
+ s = va( "%d", fps );
+ w = CG_Text_Width( "0", scale, 0 );
+ strLength = CG_DrawStrlen( s );
+ totalWidth = CG_Text_Width( FPS_STRING, scale, 0 ) + w * strLength;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - totalWidth;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ if( scalableText )
+ {
+ for( i = 0; i < strLength; i++ )
+ {
+ char c[ 2 ];
+
+ c[ 0 ] = s[ i ];
+ c[ 1 ] = '\0';
+
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle );
+ }
+ }
+ else
+ {
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, fps );
+ trap_R_SetColor( NULL );
+ }
+
+ if( scalableText )
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, FPS_STRING, 0, 0, textStyle );
+ }
+}
+
+
+/*
+=================
+CG_DrawTimerMins
+=================
+*/
+static void CG_DrawTimerMins( rectDef_t *rect, vec4_t color )
+{
+ int mins, seconds;
+ int msec;
+
+ if( !cg_drawTimer.integer )
+ return;
+
+ msec = cg.time - cgs.levelStartTime;
+
+ seconds = msec / 1000;
+ mins = seconds / 60;
+ seconds -= mins * 60;
+
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, mins );
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+=================
+CG_DrawTimerSecs
+=================
+*/
+static void CG_DrawTimerSecs( rectDef_t *rect, vec4_t color )
+{
+ int mins, seconds;
+ int msec;
+
+ if( !cg_drawTimer.integer )
+ return;
+
+ msec = cg.time - cgs.levelStartTime;
+
+ seconds = msec / 1000;
+ mins = seconds / 60;
+ seconds -= mins * 60;
+
+ trap_R_SetColor( color );
+ CG_DrawFieldPadded( rect->x, rect->y, 2, rect->w / 2, rect->h, seconds );
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+=================
+CG_DrawTimer
+=================
+*/
+static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle )
+{
+ char *s;
+ int i, tx, w, totalWidth, strLength;
+ int mins, seconds, tens;
+ int msec;
+
+ if( !cg_drawTimer.integer )
+ return;
+
+ msec = cg.time - cgs.levelStartTime;
+
+ seconds = msec / 1000;
+ mins = seconds / 60;
+ seconds -= mins * 60;
+ tens = seconds / 10;
+ seconds -= tens * 10;
+
+ s = va( "%d:%d%d", mins, tens, seconds );
+ w = CG_Text_Width( "0", scale, 0 );
+ strLength = CG_DrawStrlen( s );
+ totalWidth = w * strLength;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - totalWidth;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ for( i = 0; i < strLength; i++ )
+ {
+ char c[ 2 ];
+
+ c[ 0 ] = s[ i ];
+ c[ 1 ] = '\0';
+
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle );
+ }
+}
+
+/*
+=================
+CG_DrawClock
+=================
+*/
+static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle )
+{
+ char *s;
+ int i, tx, w, totalWidth, strLength;
+ qtime_t qt;
+ int t;
+
+ if( !cg_drawClock.integer )
+ return;
+
+ t = trap_RealTime( &qt );
+
+ if( cg_drawClock.integer == 2 )
+ {
+ s = va( "%02d%s%02d", qt.tm_hour, ( qt.tm_sec % 2 ) ? ":" : " ",
+ qt.tm_min );
+ }
+ else
+ {
+ char *pm = "am";
+ int h = qt.tm_hour;
+
+ if( h == 0 )
+ h = 12;
+ else if( h == 12 )
+ pm = "pm";
+ else if( h > 12 )
+ {
+ h -= 12;
+ pm = "pm";
+ }
+
+ s = va( "%d%s%02d%s", h, ( qt.tm_sec % 2 ) ? ":" : " ", qt.tm_min, pm );
+ }
+ w = CG_Text_Width( "0", scale, 0 );
+ strLength = CG_DrawStrlen( s );
+ totalWidth = w * strLength;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - totalWidth;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ for( i = 0; i < strLength; i++ )
+ {
+ char c[ 2 ];
+
+ c[ 0 ] = s[ i ];
+ c[ 1 ] = '\0';
+
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle );
+ }
+}
+
+/*
+==================
+CG_DrawSnapshot
+==================
+*/
+static void CG_DrawSnapshot( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle )
+{
+ char *s;
+ int w, tx;
+
+ if( !cg_drawSnapshot.integer )
+ return;
+
+ s = va( "time:%d snap:%d cmd:%d", cg.snap->serverTime,
+ cg.latestSnapshotNum, cgs.serverCommandSequence );
+ w = CG_Text_Width( s, scale, 0 );
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle );
+}
+
+/*
+===============================================================================
+
+LAGOMETER
+
+===============================================================================
+*/
+
+#define LAG_SAMPLES 128
+
+typedef struct
+{
+ int frameSamples[ LAG_SAMPLES ];
+ int frameCount;
+ int snapshotFlags[ LAG_SAMPLES ];
+ int snapshotSamples[ LAG_SAMPLES ];
+ int snapshotCount;
+} lagometer_t;
+
+lagometer_t lagometer;
+
+/*
+==============
+CG_AddLagometerFrameInfo
+
+Adds the current interpolate / extrapolate bar for this frame
+==============
+*/
+void CG_AddLagometerFrameInfo( void )
+{
+ int offset;
+
+ offset = cg.time - cg.latestSnapshotTime;
+ lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1 ) ] = offset;
+ lagometer.frameCount++;
+}
+
+/*
+==============
+CG_AddLagometerSnapshotInfo
+
+Each time a snapshot is received, log its ping time and
+the number of snapshots that were dropped before it.
+
+Pass NULL for a dropped packet.
+==============
+*/
+#define PING_FRAMES 40
+void CG_AddLagometerSnapshotInfo( snapshot_t *snap )
+{
+ static int previousPings[ PING_FRAMES ];
+ static int index;
+ int i;
+
+ // dropped packet
+ if( !snap )
+ {
+ lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = -1;
+ lagometer.snapshotCount++;
+ return;
+ }
+
+ // add this snapshot's info
+ lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->ping;
+ lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->snapFlags;
+ lagometer.snapshotCount++;
+
+ cg.ping = 0;
+ if( cg.snap )
+ {
+ previousPings[ index++ ] = cg.snap->ping;
+ index = index % PING_FRAMES;
+
+ for( i = 0; i < PING_FRAMES; i++ )
+ {
+ cg.ping += previousPings[ i ];
+ }
+
+ cg.ping /= PING_FRAMES;
+ }
+}
+
+/*
+==============
+CG_DrawDisconnect
+
+Should we draw something differnet for long lag vs no packets?
+==============
+*/
+static void CG_DrawDisconnect( void )
+{
+ float x, y;
+ int cmdNum;
+ usercmd_t cmd;
+ const char *s;
+ int w;
+ vec4_t color = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+ // draw the phone jack if we are completely past our buffers
+ cmdNum = trap_GetCurrentCmdNumber( ) - CMD_BACKUP + 1;
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ // special check for map_restart
+ if( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time )
+ return;
+
+ // also add text in center of screen
+ s = "Connection Interrupted";
+ w = CG_Text_Width( s, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ // blink the icon
+ if( ( cg.time >> 9 ) & 1 )
+ return;
+
+ x = 640 - 48;
+ y = 480 - 48;
+
+ CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader( "gfx/2d/net.tga" ) );
+}
+
+#define MAX_LAGOMETER_PING 900
+#define MAX_LAGOMETER_RANGE 300
+
+
+/*
+==============
+CG_DrawLagometer
+==============
+*/
+static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t textColor )
+{
+ int a, x, y, i;
+ float v;
+ float ax, ay, aw, ah, mid, range;
+ int color;
+ vec4_t adjustedColor;
+ float vscale;
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ return;
+
+ if( !cg_lagometer.integer )
+ return;
+
+ if( cg.demoPlayback )
+ return;
+
+ Vector4Copy( textColor, adjustedColor );
+ adjustedColor[ 3 ] = 0.25f;
+
+ trap_R_SetColor( adjustedColor );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader );
+ trap_R_SetColor( NULL );
+
+ //
+ // draw the graph
+ //
+ ax = x = rect->x;
+ ay = y = rect->y;
+ aw = rect->w;
+ ah = rect->h;
+
+ trap_R_SetColor( NULL );
+
+ CG_AdjustFrom640( &ax, &ay, &aw, &ah );
+
+ color = -1;
+ range = ah / 3;
+ mid = ay + range;
+
+ vscale = range / MAX_LAGOMETER_RANGE;
+
+ // draw the frame interpoalte / extrapolate graph
+ for( a = 0 ; a < aw ; a++ )
+ {
+ i = ( lagometer.frameCount - 1 - a ) & ( LAG_SAMPLES - 1 );
+ v = lagometer.frameSamples[ i ];
+ v *= vscale;
+
+ if( v > 0 )
+ {
+ if( color != 1 )
+ {
+ color = 1;
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] );
+ }
+
+ if( v > range )
+ v = range;
+
+ trap_R_DrawStretchPic( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ else if( v < 0 )
+ {
+ if( color != 2 )
+ {
+ color = 2;
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_BLUE ) ] );
+ }
+
+ v = -v;
+ if( v > range )
+ v = range;
+
+ trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ }
+
+ // draw the snapshot latency / drop graph
+ range = ah / 2;
+ vscale = range / MAX_LAGOMETER_PING;
+
+ for( a = 0 ; a < aw ; a++ )
+ {
+ i = ( lagometer.snapshotCount - 1 - a ) & ( LAG_SAMPLES - 1 );
+ v = lagometer.snapshotSamples[ i ];
+
+ if( v > 0 )
+ {
+ if( lagometer.snapshotFlags[ i ] & SNAPFLAG_RATE_DELAYED )
+ {
+ if( color != 5 )
+ {
+ color = 5; // YELLOW for rate delay
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] );
+ }
+ }
+ else
+ {
+ if( color != 3 )
+ {
+ color = 3;
+
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_GREEN ) ] );
+ }
+ }
+
+ v = v * vscale;
+
+ if( v > range )
+ v = range;
+
+ trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ else if( v < 0 )
+ {
+ if( color != 4 )
+ {
+ color = 4; // RED for dropped snapshots
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] );
+ }
+
+ trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ }
+
+ trap_R_SetColor( NULL );
+
+ if( cg_nopredict.integer || cg_synchronousClients.integer )
+ CG_Text_Paint( ax, ay, 0.5, white, "snc", 0, 0, ITEM_TEXTSTYLE_NORMAL );
+ else
+ {
+ char *s;
+
+ s = va( "%d", cg.ping );
+ ax = rect->x + ( rect->w / 2.0f ) - ( CG_Text_Width( s, scale, 0 ) / 2.0f ) + text_x;
+ ay = rect->y + ( rect->h / 2.0f ) + ( CG_Text_Height( s, scale, 0 ) / 2.0f ) + text_y;
+
+ Vector4Copy( textColor, adjustedColor );
+ adjustedColor[ 3 ] = 0.5f;
+ CG_Text_Paint( ax, ay, scale, adjustedColor, s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+ }
+
+ CG_DrawDisconnect( );
+}
+
+/*
+==============
+CG_DrawTextBlock
+==============
+*/
+static void CG_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle, const char *text,
+ menuDef_t *parent, itemDef_t *textItem )
+{
+ float x, y, w, h;
+
+ //offset the text
+ x = rect->x;
+ y = rect->y;
+ w = rect->w - ( 16 + ( 2 * text_x ) ); //16 to ensure text within frame
+ h = rect->h;
+
+ textItem->text = text;
+
+ textItem->parent = parent;
+ memcpy( textItem->window.foreColor, color, sizeof( vec4_t ) );
+ textItem->window.flags = 0;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ textItem->window.rect.x = x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ textItem->window.rect.x = x + w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ textItem->window.rect.x = x + ( w / 2 );
+ break;
+
+ default:
+ textItem->window.rect.x = x;
+ break;
+ }
+
+ textItem->window.rect.y = y;
+ textItem->window.rect.w = w;
+ textItem->window.rect.h = h;
+ textItem->window.borderSize = 0;
+ textItem->textRect.x = 0;
+ textItem->textRect.y = 0;
+ textItem->textRect.w = 0;
+ textItem->textRect.h = 0;
+ textItem->textalignment = align;
+ textItem->textalignx = text_x;
+ textItem->textaligny = text_y;
+ textItem->textscale = scale;
+ textItem->textStyle = textStyle;
+
+ //hack to utilise existing autowrap code
+ Item_Text_AutoWrapped_Paint( textItem );
+}
+
+/*
+===================
+CG_DrawConsole
+===================
+*/
+static void CG_DrawConsole( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle )
+{
+ static menuDef_t dummyParent;
+ static itemDef_t textItem;
+
+ CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle,
+ cg.consoleText, &dummyParent, &textItem );
+}
+
+/*
+===================
+CG_DrawTutorial
+===================
+*/
+static void CG_DrawTutorial( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle )
+{
+ static menuDef_t dummyParent;
+ static itemDef_t textItem;
+
+ if( !cg_tutorial.integer )
+ return;
+
+ CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle,
+ CG_TutorialText( ), &dummyParent, &textItem );
+}
+
+/*
+===================
+CG_DrawWeaponIcon
+===================
+*/
+void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color )
+{
+ int ammo, clips, maxAmmo;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ ammo = ps->ammo;
+ clips = ps->clips;
+ BG_FindAmmoForWeapon( cent->currentState.weapon, &maxAmmo, NULL );
+
+ // don't display if dead
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ if( cent->currentState.weapon == 0 )
+ return;
+
+ CG_RegisterWeapon( cent->currentState.weapon );
+
+ if( clips == 0 && !BG_FindInfinteAmmoForWeapon( cent->currentState.weapon ) )
+ {
+ float ammoPercent = (float)ammo / (float)maxAmmo;
+
+ if( ammoPercent < 0.33f )
+ {
+ color[ 0 ] = 1.0f;
+ color[ 1 ] = color[ 2 ] = 0.0f;
+ }
+ }
+
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && CG_AtHighestClass( ) )
+ {
+ if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME )
+ {
+ if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 )
+ color[ 3 ] = 0.0f;
+ }
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].weaponIcon );
+ trap_R_SetColor( NULL );
+}
+
+
+
+/*
+================================================================================
+
+CROSSHAIR
+
+================================================================================
+*/
+
+
+/*
+=================
+CG_DrawCrosshair
+=================
+*/
+static void CG_DrawCrosshair( void )
+{
+ float w, h;
+ qhandle_t hShader;
+ float x, y;
+ weaponInfo_t *wi;
+
+ if( cg_drawCrosshair.integer == CROSSHAIR_ALWAYSOFF )
+ return;
+
+ if( cg_drawCrosshair.integer == CROSSHAIR_RANGEDONLY &&
+ !BG_FindLongRangedForWeapon( cg.snap->ps.weapon ) )
+ {
+ return;
+ }
+
+ if( ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) ||
+ ( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) ||
+ ( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) )
+ return;
+
+ if( cg.renderingThirdPerson )
+ return;
+
+ wi = &cg_weapons[ cg.snap->ps.weapon ];
+
+ w = h = wi->crossHairSize;
+
+ x = cg_crosshairX.integer;
+ y = cg_crosshairY.integer;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+
+ hShader = wi->crossHair;
+
+ if( hShader != 0 )
+ {
+ trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * ( cg.refdef.width - w ),
+ y + cg.refdef.y + 0.5 * ( cg.refdef.height - h ),
+ w, h, 0, 0, 1, 1, hShader );
+ }
+}
+
+
+
+/*
+=================
+CG_ScanForCrosshairEntity
+=================
+*/
+static void CG_ScanForCrosshairEntity( void )
+{
+ trace_t trace;
+ vec3_t start, end;
+ int content;
+ pTeam_t team;
+
+ VectorCopy( cg.refdef.vieworg, start );
+ VectorMA( start, 131072, cg.refdef.viewaxis[ 0 ], end );
+
+ CG_Trace( &trace, start, vec3_origin, vec3_origin, end,
+ cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY );
+
+ if( trace.entityNum >= MAX_CLIENTS )
+ return;
+
+ // if the player is in fog, don't show it
+ content = trap_CM_PointContents( trace.endpos, 0 );
+ if( content & CONTENTS_FOG )
+ return;
+
+ team = cgs.clientinfo[ trace.entityNum ].team;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] != TEAM_SPECTATOR )
+ {
+ //only display team names of those on the same team as this player
+ if( team != cg.snap->ps.stats[ STAT_PTEAM ] )
+ return;
+ }
+
+ // update the fade timer
+ cg.crosshairClientNum = trace.entityNum;
+ cg.crosshairClientTime = cg.time;
+}
+
+
+/*
+=====================
+CG_DrawCrosshairNames
+=====================
+*/
+static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle )
+{
+ float *color;
+ char *name;
+ float w, x;
+
+ if( !cg_drawCrosshairNames.integer )
+ return;
+
+ if( cg.renderingThirdPerson )
+ return;
+
+ // scan the known entities to see if the crosshair is sighted on one
+ CG_ScanForCrosshairEntity( );
+
+ // draw the name of the player being looked at
+ color = CG_FadeColor( cg.crosshairClientTime, 1000 );
+ if( !color )
+ {
+ trap_R_SetColor( NULL );
+ return;
+ }
+
+ name = cgs.clientinfo[ cg.crosshairClientNum ].name;
+ w = CG_Text_Width( name, scale, 0 );
+ x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+===============
+CG_OwnerDraw
+
+Draw an owner drawn item
+===============
+*/
+void CG_OwnerDraw( float x, float y, float w, float h, float text_x,
+ float text_y, int ownerDraw, int ownerDrawFlags,
+ int align, float special, float scale, vec4_t color,
+ qhandle_t shader, int textStyle )
+{
+ rectDef_t rect;
+
+ if( cg_drawStatus.integer == 0 )
+ return;
+
+ rect.x = x;
+ rect.y = y;
+ rect.w = w;
+ rect.h = h;
+
+ switch( ownerDraw )
+ {
+ case CG_PLAYER_CREDITS_VALUE:
+ CG_DrawPlayerCreditsValue( &rect, color, qtrue );
+ break;
+ case CG_PLAYER_BANK_VALUE:
+ CG_DrawPlayerBankValue( &rect, color, qtrue );
+ break;
+ case CG_PLAYER_CREDITS_VALUE_NOPAD:
+ CG_DrawPlayerCreditsValue( &rect, color, qfalse );
+ break;
+ case CG_PLAYER_BANK_VALUE_NOPAD:
+ CG_DrawPlayerBankValue( &rect, color, qfalse );
+ break;
+ case CG_PLAYER_STAMINA:
+ CG_DrawPlayerStamina( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_PLAYER_STAMINA_1:
+ CG_DrawPlayerStamina1( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_2:
+ CG_DrawPlayerStamina2( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_3:
+ CG_DrawPlayerStamina3( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_4:
+ CG_DrawPlayerStamina4( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_BOLT:
+ CG_DrawPlayerStaminaBolt( &rect, color, shader );
+ break;
+ case CG_PLAYER_AMMO_VALUE:
+ CG_DrawPlayerAmmoValue( &rect, color );
+ break;
+ case CG_PLAYER_CLIPS_VALUE:
+ CG_DrawPlayerClipsValue( &rect, color );
+ break;
+ case CG_PLAYER_BUILD_TIMER:
+ CG_DrawPlayerBuildTimer( &rect, color );
+ break;
+ case CG_PLAYER_HEALTH:
+ CG_DrawPlayerHealthValue( &rect, color );
+ break;
+ case CG_PLAYER_HEALTH_BAR:
+ CG_DrawPlayerHealthBar( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_PLAYER_HEALTH_CROSS:
+ CG_DrawPlayerHealthCross( &rect, color, shader );
+ break;
+ case CG_PLAYER_CLIPS_RING:
+ CG_DrawPlayerClipsRing( &rect, color, shader );
+ break;
+ case CG_PLAYER_BUILD_TIMER_RING:
+ CG_DrawPlayerBuildTimerRing( &rect, color, shader );
+ break;
+ case CG_PLAYER_WALLCLIMBING:
+ CG_DrawPlayerWallclimbing( &rect, color, shader );
+ break;
+ case CG_PLAYER_BOOSTED:
+ CG_DrawPlayerBoosted( &rect, color, shader );
+ break;
+ case CG_PLAYER_BOOST_BOLT:
+ CG_DrawPlayerBoosterBolt( &rect, color, shader );
+ break;
+ case CG_PLAYER_POISON_BARBS:
+ CG_DrawPlayerPoisonBarbs( &rect, color, shader );
+ break;
+ case CG_PLAYER_ALIEN_SENSE:
+ CG_DrawAlienSense( &rect );
+ break;
+ case CG_PLAYER_HUMAN_SCANNER:
+ CG_DrawHumanScanner( &rect, shader, color );
+ break;
+ case CG_PLAYER_USABLE_BUILDABLE:
+ CG_DrawUsableBuildable( &rect, shader, color );
+ break;
+ case CG_KILLER:
+ CG_DrawKiller( &rect, scale, color, shader, textStyle );
+ break;
+ case CG_PLAYER_SELECT:
+ CG_DrawItemSelect( &rect, color );
+ break;
+ case CG_PLAYER_WEAPONICON:
+ CG_DrawWeaponIcon( &rect, color );
+ break;
+ case CG_PLAYER_SELECTTEXT:
+ CG_DrawItemSelectText( &rect, scale, textStyle );
+ break;
+ case CG_SPECTATORS:
+ CG_DrawTeamSpectators( &rect, scale, color, shader );
+ break;
+ case CG_PLAYER_CROSSHAIRNAMES:
+ CG_DrawCrosshairNames( &rect, scale, textStyle );
+ break;
+ case CG_STAGE_REPORT_TEXT:
+ CG_DrawStageReport( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ //loading screen
+ case CG_LOAD_LEVELSHOT:
+ CG_DrawLevelShot( &rect );
+ break;
+ case CG_LOAD_MEDIA:
+ CG_DrawMediaProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_MEDIA_LABEL:
+ CG_DrawMediaProgressLabel( &rect, text_x, text_y, color, scale, align );
+ break;
+ case CG_LOAD_BUILDABLES:
+ CG_DrawBuildablesProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_BUILDABLES_LABEL:
+ CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, color, scale, align );
+ break;
+ case CG_LOAD_CHARMODEL:
+ CG_DrawCharModelProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_CHARMODEL_LABEL:
+ CG_DrawCharModelProgressLabel( &rect, text_x, text_y, color, scale, align );
+ break;
+ case CG_LOAD_OVERALL:
+ CG_DrawOverallProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_LEVELNAME:
+ CG_DrawLevelName( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+ case CG_LOAD_MOTD:
+ CG_DrawMOTD( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+ case CG_LOAD_HOSTNAME:
+ CG_DrawHostname( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ case CG_FPS:
+ CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qtrue );
+ break;
+ case CG_FPS_FIXED:
+ CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qfalse );
+ break;
+ case CG_TIMER:
+ CG_DrawTimer( &rect, text_x, text_y, scale, color, align, textStyle );
+ break;
+ case CG_CLOCK:
+ CG_DrawClock( &rect, text_x, text_y, scale, color, align, textStyle );
+ break;
+ case CG_TIMER_MINS:
+ CG_DrawTimerMins( &rect, color );
+ break;
+ case CG_TIMER_SECS:
+ CG_DrawTimerSecs( &rect, color );
+ break;
+ case CG_SNAPSHOT:
+ CG_DrawSnapshot( &rect, text_x, text_y, scale, color, align, textStyle );
+ break;
+ case CG_LAGOMETER:
+ CG_DrawLagometer( &rect, text_x, text_y, scale, color );
+ break;
+
+ case CG_DEMO_PLAYBACK:
+ CG_DrawDemoPlayback( &rect, color, shader );
+ break;
+ case CG_DEMO_RECORDING:
+ CG_DrawDemoRecording( &rect, color, shader );
+ break;
+
+ case CG_CONSOLE:
+ CG_DrawConsole( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ case CG_TUTORIAL:
+ CG_DrawTutorial( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ default:
+ break;
+ }
+}
+
+void CG_MouseEvent( int x, int y )
+{
+ int n;
+
+ if( ( cg.predictedPlayerState.pm_type == PM_NORMAL ||
+ cg.predictedPlayerState.pm_type == PM_SPECTATOR ) &&
+ cg.showScores == qfalse )
+ {
+ trap_Key_SetCatcher( 0 );
+ return;
+ }
+
+ cgs.cursorX += x;
+ if( cgs.cursorX < 0 )
+ cgs.cursorX = 0;
+ else if( cgs.cursorX > 640 )
+ cgs.cursorX = 640;
+
+ cgs.cursorY += y;
+ if( cgs.cursorY < 0 )
+ cgs.cursorY = 0;
+ else if( cgs.cursorY > 480 )
+ cgs.cursorY = 480;
+
+ n = Display_CursorType( cgs.cursorX, cgs.cursorY );
+ cgs.activeCursor = 0;
+ if( n == CURSOR_ARROW )
+ cgs.activeCursor = cgs.media.selectCursor;
+ else if( n == CURSOR_SIZER )
+ cgs.activeCursor = cgs.media.sizeCursor;
+
+ if( cgs.capturedItem )
+ Display_MouseMove( cgs.capturedItem, x, y );
+ else
+ Display_MouseMove( NULL, cgs.cursorX, cgs.cursorY );
+}
+
+/*
+==================
+CG_HideTeamMenus
+==================
+
+*/
+void CG_HideTeamMenu( void )
+{
+ Menus_CloseByName( "teamMenu" );
+ Menus_CloseByName( "getMenu" );
+}
+
+/*
+==================
+CG_ShowTeamMenus
+==================
+
+*/
+void CG_ShowTeamMenu( void )
+{
+ Menus_OpenByName( "teamMenu" );
+}
+
+/*
+==================
+CG_EventHandling
+==================
+ type 0 - no event handling
+ 1 - team menu
+ 2 - hud editor
+
+*/
+void CG_EventHandling( int type )
+{
+ cgs.eventHandling = type;
+
+ if( type == CGAME_EVENT_NONE )
+ CG_HideTeamMenu( );
+}
+
+
+
+void CG_KeyEvent( int key, qboolean down )
+{
+ if( !down )
+ return;
+
+ if( cg.predictedPlayerState.pm_type == PM_NORMAL ||
+ ( cg.predictedPlayerState.pm_type == PM_SPECTATOR &&
+ cg.showScores == qfalse ) )
+ {
+ CG_EventHandling( CGAME_EVENT_NONE );
+ trap_Key_SetCatcher( 0 );
+ return;
+ }
+
+ Display_HandleKey( key, down, cgs.cursorX, cgs.cursorY );
+
+ if( cgs.capturedItem )
+ cgs.capturedItem = NULL;
+ else
+ {
+ if( key == K_MOUSE2 && down )
+ cgs.capturedItem = Display_CaptureItem( cgs.cursorX, cgs.cursorY );
+ }
+}
+
+int CG_ClientNumFromName( const char *p )
+{
+ int i;
+
+ for( i = 0; i < cgs.maxclients; i++ )
+ {
+ if( cgs.clientinfo[ i ].infoValid &&
+ Q_stricmp( cgs.clientinfo[ i ].name, p ) == 0 )
+ return i;
+ }
+
+ return -1;
+}
+
+void CG_RunMenuScript( char **args )
+{
+}
+
+
+void CG_GetTeamColor( vec4_t *color )
+{
+ (*color)[ 0 ] = (*color)[ 2 ] = 0.0f;
+ (*color)[ 1 ] = 0.17f;
+ (*color)[ 3 ] = 0.25f;
+}
+//END TA UI
+
+
+/*
+================
+CG_DrawLighting
+
+================
+*/
+static void CG_DrawLighting( void )
+{
+ centity_t *cent;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+
+ //fade to black if stamina is low
+ if( ( cg.snap->ps.stats[ STAT_STAMINA ] < -800 ) &&
+ ( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) )
+ {
+ vec4_t black = { 0, 0, 0, 0 };
+ black[ 3 ] = 1.0 - ( (float)( cg.snap->ps.stats[ STAT_STAMINA ] + 1000 ) / 200.0f );
+ trap_R_SetColor( black );
+ CG_DrawPic( 0, 0, 640, 480, cgs.media.whiteShader );
+ trap_R_SetColor( NULL );
+ }
+}
+
+/*
+===============================================================================
+
+CENTER PRINTING
+
+===============================================================================
+*/
+
+
+/*
+==============
+CG_CenterPrint
+
+Called for important messages that should stay in the center of the screen
+for a few moments
+==============
+*/
+void CG_CenterPrint( const char *str, int y, int charWidth )
+{
+ char *s;
+
+ Q_strncpyz( cg.centerPrint, str, sizeof( cg.centerPrint ) );
+
+ cg.centerPrintTime = cg.time;
+ cg.centerPrintY = y;
+ cg.centerPrintCharWidth = charWidth;
+
+ // count the number of lines for centering
+ cg.centerPrintLines = 1;
+ s = cg.centerPrint;
+ while( *s )
+ {
+ if( *s == '\n' )
+ cg.centerPrintLines++;
+
+ s++;
+ }
+}
+
+
+/*
+===================
+CG_DrawCenterString
+===================
+*/
+static void CG_DrawCenterString( void )
+{
+ char *start;
+ int l;
+ int x, y, w;
+ int h;
+ float *color;
+
+ if( !cg.centerPrintTime )
+ return;
+
+ color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value );
+ if( !color )
+ return;
+
+ trap_R_SetColor( color );
+
+ start = cg.centerPrint;
+
+ y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2;
+
+ while( 1 )
+ {
+ char linebuffer[ 1024 ];
+
+ for( l = 0; l < 50; l++ )
+ {
+ if( !start[ l ] || start[ l ] == '\n' )
+ break;
+
+ linebuffer[ l ] = start[ l ];
+ }
+
+ linebuffer[ l ] = 0;
+
+ w = CG_Text_Width( linebuffer, 0.5, 0 );
+ h = CG_Text_Height( linebuffer, 0.5, 0 );
+ x = ( SCREEN_WIDTH - w ) / 2;
+ CG_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE );
+ y += h + 6;
+
+ while( *start && ( *start != '\n' ) )
+ start++;
+
+ if( !*start )
+ break;
+
+ start++;
+ }
+
+ trap_R_SetColor( NULL );
+}
+
+
+
+
+
+//==============================================================================
+
+//FIXME: both vote notes are hardcoded, change to ownerdrawn?
+
+/*
+=================
+CG_DrawVote
+=================
+*/
+static void CG_DrawVote( void )
+{
+ char *s;
+ int sec;
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+ char yeskey[ 32 ], nokey[ 32 ];
+
+ if( !cgs.voteTime )
+ return;
+
+ // play a talk beep whenever it is modified
+ if( cgs.voteModified )
+ {
+ cgs.voteModified = qfalse;
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ }
+
+ sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000;
+
+ if( sec < 0 )
+ sec = 0;
+ Q_strncpyz( yeskey, CG_KeyBinding( "vote yes" ), sizeof( yeskey ) );
+ Q_strncpyz( nokey, CG_KeyBinding( "vote no" ), sizeof( nokey ) );
+ s = va( "VOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec, cgs.voteString,
+ yeskey, cgs.voteYes, nokey, cgs.voteNo );
+ CG_Text_Paint( 8, 340, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+}
+
+/*
+=================
+CG_DrawTeamVote
+=================
+*/
+static void CG_DrawTeamVote( void )
+{
+ char *s;
+ int sec, cs_offset;
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+ char yeskey[ 32 ], nokey[ 32 ];
+
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ cs_offset = 0;
+ else if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ cs_offset = 1;
+ else
+ return;
+
+ if( !cgs.teamVoteTime[ cs_offset ] )
+ return;
+
+ // play a talk beep whenever it is modified
+ if ( cgs.teamVoteModified[ cs_offset ] )
+ {
+ cgs.teamVoteModified[ cs_offset ] = qfalse;
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ }
+
+ sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[ cs_offset ] ) ) / 1000;
+
+ if( sec < 0 )
+ sec = 0;
+
+ Q_strncpyz( yeskey, CG_KeyBinding( "teamvote yes" ), sizeof( yeskey ) );
+ Q_strncpyz( nokey, CG_KeyBinding( "teamvote no" ), sizeof( nokey ) );
+ s = va( "TEAMVOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec,
+ cgs.teamVoteString[ cs_offset ],
+ yeskey, cgs.teamVoteYes[cs_offset],
+ nokey, cgs.teamVoteNo[ cs_offset ] );
+
+ CG_Text_Paint( 8, 360, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+}
+
+
+static qboolean CG_DrawScoreboard( void )
+{
+ static qboolean firstTime = qtrue;
+ float fade, *fadeColor;
+
+ if( menuScoreboard )
+ menuScoreboard->window.flags &= ~WINDOW_FORCED;
+
+ if( cg_paused.integer )
+ {
+ cg.deferredPlayerLoading = 0;
+ firstTime = qtrue;
+ return qfalse;
+ }
+
+ if( cg.showScores ||
+ cg.predictedPlayerState.pm_type == PM_INTERMISSION )
+ {
+ fade = 1.0;
+ fadeColor = colorWhite;
+ }
+ else
+ {
+ cg.deferredPlayerLoading = 0;
+ cg.killerName[ 0 ] = 0;
+ firstTime = qtrue;
+ return qfalse;
+ }
+
+
+ if( menuScoreboard == NULL )
+ menuScoreboard = Menus_FindByName( "teamscore_menu" );
+
+ if( menuScoreboard )
+ {
+ if( firstTime )
+ {
+ CG_SetScoreSelection( menuScoreboard );
+ firstTime = qfalse;
+ }
+
+ Menu_Paint( menuScoreboard, qtrue );
+ }
+
+ return qtrue;
+}
+
+/*
+=================
+CG_DrawIntermission
+=================
+*/
+static void CG_DrawIntermission( void )
+{
+ if( cg_drawStatus.integer )
+ Menu_Paint( Menus_FindByName( "default_hud" ), qtrue );
+
+ cg.scoreFadeTime = cg.time;
+ cg.scoreBoardShowing = CG_DrawScoreboard( );
+}
+
+#define FOLLOWING_STRING "following "
+
+/*
+=================
+CG_DrawFollow
+=================
+*/
+static qboolean CG_DrawFollow( void )
+{
+ float w;
+ vec4_t color;
+ char buffer[ MAX_STRING_CHARS ];
+
+ if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ return qfalse;
+
+ color[ 0 ] = 1;
+ color[ 1 ] = 1;
+ color[ 2 ] = 1;
+ color[ 3 ] = 1;
+
+ strcpy( buffer, FOLLOWING_STRING );
+ strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name );
+
+ w = CG_Text_Width( buffer, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ return qtrue;
+}
+
+/*
+=================
+CG_DrawQueue
+=================
+*/
+static qboolean CG_DrawQueue( void )
+{
+ float w;
+ vec4_t color;
+ char buffer[ MAX_STRING_CHARS ];
+
+ if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) )
+ return qfalse;
+
+ color[ 0 ] = 1;
+ color[ 1 ] = 1;
+ color[ 2 ] = 1;
+ color[ 3 ] = 1;
+
+ Com_sprintf( buffer, MAX_STRING_CHARS, "You are in position %d of the spawn queue.",
+ cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1 );
+
+ w = CG_Text_Width( buffer, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if( cgs.numAlienSpawns == 1 )
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." );
+ else
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.",
+ cgs.numAlienSpawns );
+ }
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( cgs.numHumanSpawns == 1 )
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." );
+ else
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.",
+ cgs.numHumanSpawns );
+ }
+
+ w = CG_Text_Width( buffer, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ return qtrue;
+}
+
+//==================================================================================
+
+#define SPECTATOR_STRING "SPECTATOR"
+/*
+=================
+CG_Draw2D
+=================
+*/
+static void CG_Draw2D( void )
+{
+ vec4_t color;
+ float w;
+ menuDef_t *menu = NULL, *defaultMenu;
+
+ color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 1.0f;
+
+ // if we are taking a levelshot for the menu, don't draw anything
+ if( cg.levelShot )
+ return;
+
+ if( cg_draw2D.integer == 0 )
+ return;
+
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ {
+ CG_DrawIntermission( );
+ return;
+ }
+
+ //TA: draw the lighting effects e.g. nvg
+ CG_DrawLighting( );
+
+
+ defaultMenu = Menus_FindByName( "default_hud" );
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ {
+ w = CG_Text_Width( SPECTATOR_STRING, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 440, 0.7f, color, SPECTATOR_STRING, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+ }
+ else
+ menu = Menus_FindByName( BG_FindHudNameForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ) );
+
+ if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) &&
+ !( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) && menu &&
+ ( cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) )
+ {
+ CG_DrawBuildableStatus( );
+ if( cg_drawStatus.integer )
+ Menu_Paint( menu, qtrue );
+
+ CG_DrawCrosshair( );
+ }
+ else if( cg_drawStatus.integer )
+ Menu_Paint( defaultMenu, qtrue );
+
+ CG_DrawVote( );
+ CG_DrawTeamVote( );
+ CG_DrawFollow( );
+ CG_DrawQueue( );
+
+ // don't draw center string if scoreboard is up
+ cg.scoreBoardShowing = CG_DrawScoreboard( );
+
+ if( !cg.scoreBoardShowing )
+ CG_DrawCenterString( );
+}
+
+/*
+===============
+CG_ScalePainBlendTCs
+===============
+*/
+static void CG_ScalePainBlendTCs( float* s1, float *t1, float *s2, float *t2 )
+{
+ *s1 -= 0.5f;
+ *t1 -= 0.5f;
+ *s2 -= 0.5f;
+ *t2 -= 0.5f;
+
+ *s1 *= cg_painBlendZoom.value;
+ *t1 *= cg_painBlendZoom.value;
+ *s2 *= cg_painBlendZoom.value;
+ *t2 *= cg_painBlendZoom.value;
+
+ *s1 += 0.5f;
+ *t1 += 0.5f;
+ *s2 += 0.5f;
+ *t2 += 0.5f;
+}
+
+#define PAINBLEND_BORDER 0.15f
+
+/*
+===============
+CG_PainBlend
+===============
+*/
+static void CG_PainBlend( void )
+{
+ vec4_t color;
+ int damage;
+ float damageAsFracOfMax;
+ qhandle_t shader = cgs.media.viewBloodShader;
+ float x, y, w, h;
+ float s1, t1, s2, t2;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR || cg.intermissionStarted )
+ return;
+
+ damage = cg.lastHealth - cg.snap->ps.stats[ STAT_HEALTH ];
+
+ if( damage < 0 )
+ damage = 0;
+
+ damageAsFracOfMax = (float)damage / cg.snap->ps.stats[ STAT_MAX_HEALTH ];
+ cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ];
+
+ cg.painBlendValue += damageAsFracOfMax * cg_painBlendScale.value;
+
+ if( cg.painBlendValue > 0.0f )
+ {
+ cg.painBlendValue -= ( cg.frametime / 1000.0f ) *
+ cg_painBlendDownRate.value;
+ }
+
+ if( cg.painBlendValue > 1.0f )
+ cg.painBlendValue = 1.0f;
+ else if( cg.painBlendValue <= 0.0f )
+ {
+ cg.painBlendValue = 0.0f;
+ return;
+ }
+
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ VectorSet( color, 0.43f, 0.8f, 0.37f );
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ VectorSet( color, 0.8f, 0.0f, 0.0f );
+
+ if( cg.painBlendValue > cg.painBlendTarget )
+ {
+ cg.painBlendTarget += ( cg.frametime / 1000.0f ) *
+ cg_painBlendUpRate.value;
+ }
+ else if( cg.painBlendValue < cg.painBlendTarget )
+ cg.painBlendTarget = cg.painBlendValue;
+
+ if( cg.painBlendTarget > cg_painBlendMax.value )
+ cg.painBlendTarget = cg_painBlendMax.value;
+
+ color[ 3 ] = cg.painBlendTarget;
+
+ trap_R_SetColor( color );
+
+ //left
+ x = 0.0f; y = 0.0f;
+ w = PAINBLEND_BORDER * 640.0f; h = 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = 0.0f; t1 = 0.0f;
+ s2 = PAINBLEND_BORDER; t2 = 1.0f;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ //right
+ x = 640.0f - ( PAINBLEND_BORDER * 640.0f ); y = 0.0f;
+ w = PAINBLEND_BORDER * 640.0f; h = 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = 1.0f - PAINBLEND_BORDER; t1 = 0.0f;
+ s2 = 1.0f; t2 =1.0f;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ //top
+ x = PAINBLEND_BORDER * 640.0f; y = 0.0f;
+ w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = PAINBLEND_BORDER; t1 = 0.0f;
+ s2 = 1.0f - PAINBLEND_BORDER; t2 = PAINBLEND_BORDER;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ //bottom
+ x = PAINBLEND_BORDER * 640.0f; y = 480.0f - ( PAINBLEND_BORDER * 480.0f );
+ w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = PAINBLEND_BORDER; t1 = 1.0f - PAINBLEND_BORDER;
+ s2 = 1.0f - PAINBLEND_BORDER; t2 = 1.0f;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ trap_R_SetColor( NULL );
+}
+
+/*
+=====================
+CG_ResetPainBlend
+=====================
+*/
+void CG_ResetPainBlend( void )
+{
+ cg.painBlendValue = 0.0f;
+ cg.painBlendTarget = 0.0f;
+ cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ];
+}
+
+/*
+=====================
+CG_DrawActive
+
+Perform all drawing needed to completely fill the screen
+=====================
+*/
+void CG_DrawActive( stereoFrame_t stereoView )
+{
+ float separation;
+ vec3_t baseOrg;
+
+ // optionally draw the info screen instead
+ if( !cg.snap )
+ return;
+
+ switch ( stereoView )
+ {
+ case STEREO_CENTER:
+ separation = 0;
+ break;
+ case STEREO_LEFT:
+ separation = -cg_stereoSeparation.value / 2;
+ break;
+ case STEREO_RIGHT:
+ separation = cg_stereoSeparation.value / 2;
+ break;
+ default:
+ separation = 0;
+ CG_Error( "CG_DrawActive: Undefined stereoView" );
+ }
+
+ // clear around the rendered view if sized down
+ CG_TileClear( );
+
+ // offset vieworg appropriately if we're doing stereo separation
+ VectorCopy( cg.refdef.vieworg, baseOrg );
+
+ if( separation != 0 )
+ VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[ 1 ],
+ cg.refdef.vieworg );
+
+ // draw 3D view
+ trap_R_RenderScene( &cg.refdef );
+
+ // restore original viewpoint if running stereo
+ if( separation != 0 )
+ VectorCopy( baseOrg, cg.refdef.vieworg );
+
+ // first person blend blobs, done after AnglesToAxis
+ if( !cg.renderingThirdPerson )
+ CG_PainBlend( );
+
+ // draw status bar and other floating elements
+ CG_Draw2D( );
+}
+
+
+
diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c
new file mode 100644
index 0000000..06ae071
--- /dev/null
+++ b/src/cgame/cg_drawtools.c
@@ -0,0 +1,378 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc
+
+
+#include "cg_local.h"
+
+/*
+===============
+CG_DrawPlane
+
+Draw a quad in 3 space - basically CG_DrawPic in 3 space
+===============
+*/
+void CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader )
+{
+ polyVert_t verts[ 4 ];
+ vec3_t temp;
+
+ VectorCopy( origin, 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;
+
+ VectorAdd( origin, right, temp );
+ VectorCopy( temp, 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;
+
+ VectorAdd( origin, right, temp );
+ VectorAdd( temp, down, temp );
+ VectorCopy( temp, 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;
+
+ VectorAdd( origin, down, temp );
+ VectorCopy( temp, verts[ 3 ].xyz );
+ verts[ 3 ].st[ 0 ] = 0;
+ verts[ 3 ].st[ 1 ] = 1;
+ verts[ 3 ].modulate[ 0 ] = 255;
+ verts[ 3 ].modulate[ 1 ] = 255;
+ verts[ 3 ].modulate[ 2 ] = 255;
+ verts[ 3 ].modulate[ 3 ] = 255;
+
+ trap_R_AddPolyToScene( shader, 4, verts );
+}
+
+/*
+================
+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
+ // scale for screen sizes
+ *x *= cgs.screenXScale;
+ *y *= cgs.screenYScale;
+ *w *= cgs.screenXScale;
+ *h *= cgs.screenYScale;
+}
+
+/*
+================
+CG_FillRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_FillRect( float x, float y, float width, float height, const float *color )
+{
+ trap_R_SetColor( color );
+
+ CG_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader );
+
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+================
+CG_DrawSides
+
+Coords are virtual 640x480
+================
+*/
+void CG_DrawSides( float x, float y, float w, float h, float size )
+{
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ size *= cgs.screenXScale;
+ trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader );
+ trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader );
+}
+
+void CG_DrawTopBottom( float x, float y, float w, float h, float size )
+{
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ size *= cgs.screenYScale;
+ trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader );
+ trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader );
+}
+
+
+/*
+================
+CG_DrawRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_DrawRect( float x, float y, float width, float height, float size, const float *color )
+{
+ trap_R_SetColor( color );
+
+ CG_DrawTopBottom( x, y, width, height, size );
+ CG_DrawSides( x, y, width, height, size );
+
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+================
+CG_DrawPic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader )
+{
+ CG_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+}
+
+
+
+/*
+================
+CG_DrawFadePic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor,
+ vec4_t tcolor, float amount, qhandle_t hShader )
+{
+ vec4_t finalcolor;
+ float inverse;
+
+ inverse = 100 - amount;
+
+ CG_AdjustFrom640( &x, &y, &width, &height );
+
+ finalcolor[ 0 ] = ( ( inverse * fcolor[ 0 ] ) + ( amount * tcolor[ 0 ] ) ) / 100;
+ finalcolor[ 1 ] = ( ( inverse * fcolor[ 1 ] ) + ( amount * tcolor[ 1 ] ) ) / 100;
+ finalcolor[ 2 ] = ( ( inverse * fcolor[ 2 ] ) + ( amount * tcolor[ 2 ] ) ) / 100;
+ finalcolor[ 3 ] = ( ( inverse * fcolor[ 3 ] ) + ( amount * tcolor[ 3 ] ) ) / 100;
+
+ trap_R_SetColor( finalcolor );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+=================
+CG_DrawStrlen
+
+Returns character count, skiping color escape codes
+=================
+*/
+int CG_DrawStrlen( const char *str )
+{
+ const char *s = str;
+ int count = 0;
+
+ while( *s )
+ {
+ if( Q_IsColorString( s ) )
+ s += 2;
+ else
+ {
+ count++;
+ s++;
+ }
+ }
+
+ return count;
+}
+
+/*
+=============
+CG_TileClearBox
+
+This repeats a 64*64 tile graphic to fill the screen around a sized down
+refresh window.
+=============
+*/
+static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader )
+{
+ float s1, t1, s2, t2;
+
+ s1 = x / 64.0;
+ t1 = y / 64.0;
+ s2 = ( x + w ) / 64.0;
+ t2 = ( y + h ) / 64.0;
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader );
+}
+
+
+
+/*
+==============
+CG_TileClear
+
+Clear around a sized down screen
+==============
+*/
+void CG_TileClear( void )
+{
+ int top, bottom, left, right;
+ int w, h;
+
+ w = cgs.glconfig.vidWidth;
+ h = cgs.glconfig.vidHeight;
+
+ if( cg.refdef.x == 0 && cg.refdef.y == 0 &&
+ cg.refdef.width == w && cg.refdef.height == h )
+ return; // full screen rendering
+
+ top = cg.refdef.y;
+ bottom = top + cg.refdef.height - 1;
+ left = cg.refdef.x;
+ right = left + cg.refdef.width - 1;
+
+ // clear above view screen
+ CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader );
+
+ // clear below view screen
+ CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader );
+
+ // clear left of view screen
+ CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader );
+
+ // clear right of view screen
+ CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader );
+}
+
+/*
+================
+CG_FadeColor
+================
+*/
+float *CG_FadeColor( int startMsec, int totalMsec )
+{
+ static vec4_t color;
+ int t;
+
+ if( startMsec == 0 )
+ return NULL;
+
+ t = cg.time - startMsec;
+
+ if( t >= totalMsec )
+ return NULL;
+
+ // fade out
+ if( totalMsec - t < FADE_TIME )
+ color[ 3 ] = ( totalMsec - t ) * 1.0 / FADE_TIME;
+ else
+ color[ 3 ] = 1.0;
+
+ color[ 0 ] = color[ 1 ] = color[ 2 ] = 1;
+
+ return color;
+}
+
+/*
+================
+CG_WorldToScreen
+================
+*/
+qboolean CG_WorldToScreen( vec3_t point, float *x, float *y )
+{
+ vec3_t trans;
+ float xc, yc;
+ float px, py;
+ float z;
+
+ px = tan( cg.refdef.fov_x * M_PI / 360.0 );
+ py = tan( cg.refdef.fov_y * M_PI / 360.0 );
+
+ VectorSubtract( point, cg.refdef.vieworg, trans );
+
+ xc = 640.0f / 2.0f;
+ yc = 480.0f / 2.0f;
+
+ z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] );
+ if( z <= 0.001f )
+ return qfalse;
+
+ if( x )
+ *x = xc - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px );
+
+ if( y )
+ *y = yc - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py );
+
+ return qtrue;
+}
+
+/*
+================
+CG_KeyBinding
+================
+*/
+char *CG_KeyBinding( const char *bind )
+{
+ static char key[ 32 ];
+ char bindbuff[ MAX_CVAR_VALUE_STRING ];
+ int i;
+
+ key[ 0 ] = '\0';
+ // NOTE: change K_LAST_KEY to MAX_KEYS for full key support (eventually)
+ for( i = 0; i < K_LAST_KEY; i++ )
+ {
+ trap_Key_GetBindingBuf( i, bindbuff, sizeof( bindbuff ) );
+ if( !Q_stricmp( bindbuff, bind ) )
+ {
+ trap_Key_KeynumToStringBuf( i, key, sizeof( key ) );
+ break;
+ }
+ }
+ if( !key[ 0 ] )
+ {
+ Q_strncpyz( key, "\\", sizeof( key ) );
+ Q_strcat( key, sizeof( key ), bind );
+ }
+ return key;
+}
diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c
new file mode 100644
index 0000000..17f1a7d
--- /dev/null
+++ b/src/cgame/cg_ents.c
@@ -0,0 +1,1256 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_ents.c -- present snapshot entities, happens every single frame
+
+
+#include "cg_local.h"
+
+/*
+======================
+CG_DrawBoxFace
+
+Draws a bounding box face
+======================
+*/
+static void CG_DrawBoxFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d )
+{
+ polyVert_t verts[ 4 ];
+ vec4_t color = { 255.0f, 0.0f, 0.0f, 128.0f };
+
+ VectorCopy( d, verts[ 0 ].xyz );
+ verts[ 0 ].st[ 0 ] = 1;
+ verts[ 0 ].st[ 1 ] = 1;
+ Vector4Copy( color, verts[ 0 ].modulate );
+
+ VectorCopy( c, verts[ 1 ].xyz );
+ verts[ 1 ].st[ 0 ] = 1;
+ verts[ 1 ].st[ 1 ] = 0;
+ Vector4Copy( color, verts[ 1 ].modulate );
+
+ VectorCopy( b, verts[ 2 ].xyz );
+ verts[ 2 ].st[ 0 ] = 0;
+ verts[ 2 ].st[ 1 ] = 0;
+ Vector4Copy( color, verts[ 2 ].modulate );
+
+ VectorCopy( a, verts[ 3 ].xyz );
+ verts[ 3 ].st[ 0 ] = 0;
+ verts[ 3 ].st[ 1 ] = 1;
+ Vector4Copy( color, verts[ 3 ].modulate );
+
+ trap_R_AddPolyToScene( cgs.media.outlineShader, 4, verts );
+}
+
+/*
+======================
+CG_DrawBoundingBox
+
+Draws a bounding box
+======================
+*/
+void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs )
+{
+ vec3_t ppp, mpp, mmp, pmp;
+ vec3_t mmm, pmm, ppm, mpm;
+
+ ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ mpp[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ mmp[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mmp[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ pmp[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ ppm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ mpm[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ mpm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ mmm[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mmm[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ mmm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ pmm[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ pmm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ //phew!
+
+ CG_DrawBoxFace( ppp, mpp, mmp, pmp );
+ CG_DrawBoxFace( ppp, pmp, pmm, ppm );
+ CG_DrawBoxFace( mpp, ppp, ppm, mpm );
+ CG_DrawBoxFace( mmp, mpp, mpm, mmm );
+ CG_DrawBoxFace( pmp, mmp, mmm, pmm );
+ CG_DrawBoxFace( mmm, mpm, ppm, pmm );
+}
+
+
+/*
+======================
+CG_PositionEntityOnTag
+
+Modifies the entities position and axis by the given
+tag location
+======================
+*/
+void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName )
+{
+ int i;
+ orientation_t lerped;
+
+ // lerp the tag
+ trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for( i = 0; i < 3; i++ )
+ VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );
+
+ // had to cast away the const to avoid compiler problems...
+ MatrixMultiply( lerped.axis, ( (refEntity_t *)parent )->axis, entity->axis );
+ entity->backlerp = parent->backlerp;
+}
+
+
+/*
+======================
+CG_PositionRotatedEntityOnTag
+
+Modifies the entities position and axis by the given
+tag location
+======================
+*/
+void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName )
+{
+ int i;
+ orientation_t lerped;
+ vec3_t tempAxis[ 3 ];
+
+//AxisClear( entity->axis );
+ // lerp the tag
+ trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for( i = 0; i < 3; i++ )
+ VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );
+
+ // had to cast away the const to avoid compiler problems...
+ MatrixMultiply( entity->axis, lerped.axis, tempAxis );
+ MatrixMultiply( tempAxis, ( (refEntity_t *)parent )->axis, entity->axis );
+}
+
+
+
+/*
+==========================================================================
+
+FUNCTIONS CALLED EACH FRAME
+
+==========================================================================
+*/
+
+/*
+======================
+CG_SetEntitySoundPosition
+
+Also called by event processing code
+======================
+*/
+void CG_SetEntitySoundPosition( centity_t *cent )
+{
+ if( cent->currentState.solid == SOLID_BMODEL )
+ {
+ vec3_t origin;
+ float *v;
+
+ v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
+ VectorAdd( cent->lerpOrigin, v, origin );
+ trap_S_UpdateEntityPosition( cent->currentState.number, origin );
+ }
+ else
+ trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin );
+}
+
+/*
+==================
+CG_EntityEffects
+
+Add continuous entity effects, like local entity emission and lighting
+==================
+*/
+static void CG_EntityEffects( centity_t *cent )
+{
+ // update sound origins
+ CG_SetEntitySoundPosition( cent );
+
+ // add loop sound
+ if( cent->currentState.loopSound )
+ {
+ if( cent->currentState.eType != ET_SPEAKER )
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ cgs.gameSounds[ cent->currentState.loopSound ] );
+ }
+ else
+ {
+ trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ cgs.gameSounds[ cent->currentState.loopSound ] );
+ }
+ }
+
+
+ // constant light glow
+ if ( cent->currentState.constantLight )
+ {
+ int cl;
+ int i, r, g, b;
+
+ cl = cent->currentState.constantLight;
+ r = cl & 255;
+ g = ( cl >> 8 ) & 255;
+ b = ( cl >> 16 ) & 255;
+ i = ( ( cl >> 24 ) & 255 ) * 4;
+ trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b );
+ }
+
+ if( CG_IsTrailSystemValid( &cent->muzzleTS ) )
+ {
+ //FIXME hack to prevent tesla trails reaching too far
+ if( cent->currentState.eType == ET_BUILDABLE )
+ {
+ vec3_t front, back;
+
+ CG_AttachmentPoint( &cent->muzzleTS->frontAttachment, front );
+ CG_AttachmentPoint( &cent->muzzleTS->backAttachment, back );
+
+ if( Distance( front, back ) > ( TESLAGEN_RANGE * M_ROOT3 ) )
+ CG_DestroyTrailSystem( &cent->muzzleTS );
+ }
+
+ if( cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid( &cent->muzzleTS ) )
+ CG_DestroyTrailSystem( &cent->muzzleTS );
+ }
+}
+
+
+/*
+==================
+CG_General
+==================
+*/
+static void CG_General( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // if set to invisible, skip
+ if( !s1->modelindex )
+ return;
+
+ memset( &ent, 0, sizeof( ent ) );
+
+ // set frame
+
+ ent.frame = s1->frame;
+ ent.oldframe = ent.frame;
+ ent.backlerp = 0;
+
+ VectorCopy( cent->lerpOrigin, ent.origin);
+ VectorCopy( cent->lerpOrigin, ent.oldorigin);
+
+ ent.hModel = cgs.gameModels[ s1->modelindex ];
+
+ // player model
+ if( s1->number == cg.snap->ps.clientNum )
+ ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors
+
+ // convert angles to axis
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+/*
+==================
+CG_Speaker
+
+Speaker entities can automatically play sounds
+==================
+*/
+static void CG_Speaker( centity_t *cent )
+{
+ if( ! cent->currentState.clientNum )
+ { // FIXME: use something other than clientNum...
+ return; // not auto triggering
+ }
+
+ if( cg.time < cent->miscTime )
+ return;
+
+ trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[ cent->currentState.eventParm ] );
+
+ // ent->s.frame = ent->wait * 10;
+ // ent->s.clientNum = ent->random * 10;
+ cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom( );
+}
+
+
+//============================================================================
+
+/*
+===============
+CG_LaunchMissile
+===============
+*/
+static void CG_LaunchMissile( centity_t *cent )
+{
+ entityState_t *es;
+ const weaponInfo_t *wi;
+ particleSystem_t *ps;
+ trailSystem_t *ts;
+ weapon_t weapon;
+ weaponMode_t weaponMode;
+
+ es = &cent->currentState;
+
+ weapon = es->weapon;
+ if( weapon > WP_NUM_WEAPONS )
+ weapon = WP_NONE;
+
+ wi = &cg_weapons[ weapon ];
+ weaponMode = es->generic1;
+
+ if( wi->wim[ weaponMode ].missileParticleSystem )
+ {
+ ps = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].missileParticleSystem );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentCent( &ps->attachment, cent );
+ CG_AttachToCent( &ps->attachment );
+ }
+ }
+
+ if( wi->wim[ weaponMode ].missileTrailSystem )
+ {
+ ts = CG_SpawnNewTrailSystem( wi->wim[ weaponMode ].missileTrailSystem );
+
+ if( CG_IsTrailSystemValid( &ts ) )
+ {
+ CG_SetAttachmentCent( &ts->frontAttachment, cent );
+ CG_AttachToCent( &ts->frontAttachment );
+ }
+ }
+}
+
+/*
+===============
+CG_Missile
+===============
+*/
+static void CG_Missile( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es;
+ const weaponInfo_t *wi;
+ weapon_t weapon;
+ weaponMode_t weaponMode;
+ const weaponInfoMode_t *wim;
+
+ es = &cent->currentState;
+
+ weapon = es->weapon;
+ if( weapon > WP_NUM_WEAPONS )
+ weapon = WP_NONE;
+
+ wi = &cg_weapons[ weapon ];
+ weaponMode = es->generic1;
+
+ wim = &wi->wim[ weaponMode ];
+
+ // calculate the axis
+ VectorCopy( es->angles, cent->lerpAngles );
+
+ // add dynamic light
+ if( wim->missileDlight )
+ {
+ trap_R_AddLightToScene( cent->lerpOrigin, wim->missileDlight,
+ wim->missileDlightColor[ 0 ],
+ wim->missileDlightColor[ 1 ],
+ wim->missileDlightColor[ 2 ] );
+ }
+
+ // add missile sound
+ if( wim->missileSound )
+ {
+ vec3_t velocity;
+
+ BG_EvaluateTrajectoryDelta( &cent->currentState.pos, cg.time, velocity );
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, wim->missileSound );
+ }
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+
+ if( wim->usesSpriteMissle )
+ {
+ ent.reType = RT_SPRITE;
+ ent.radius = wim->missileSpriteSize;
+ ent.rotation = 0;
+ ent.customShader = wim->missileSprite;
+ ent.shaderRGBA[ 0 ] = 0xFF;
+ ent.shaderRGBA[ 1 ] = 0xFF;
+ ent.shaderRGBA[ 2 ] = 0xFF;
+ ent.shaderRGBA[ 3 ] = 0xFF;
+ }
+ else
+ {
+ ent.hModel = wim->missileModel;
+ ent.renderfx = wim->missileRenderfx | RF_NOSHADOW;
+
+ // convert direction of travel into axis
+ if( VectorNormalize2( es->pos.trDelta, ent.axis[ 0 ] ) == 0 )
+ ent.axis[ 0 ][ 2 ] = 1;
+
+ // spin as it moves
+ if( es->pos.trType != TR_STATIONARY && wim->missileRotates )
+ RotateAroundDirection( ent.axis, cg.time / 4 );
+ else
+ RotateAroundDirection( ent.axis, es->time );
+
+ if( wim->missileAnimates )
+ {
+ int timeSinceStart = cg.time - es->time;
+
+ if( wim->missileAnimLooping )
+ {
+ ent.frame = wim->missileAnimStartFrame +
+ (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ) %
+ wim->missileAnimNumFrames;
+ }
+ else
+ {
+ ent.frame = wim->missileAnimStartFrame +
+ (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate );
+
+ if( ent.frame > ( wim->missileAnimStartFrame + wim->missileAnimNumFrames ) )
+ ent.frame = wim->missileAnimStartFrame + wim->missileAnimNumFrames;
+ }
+ }
+ }
+
+ //only refresh if there is something to display
+ if( wim->missileSprite || wim->missileModel )
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+/*
+===============
+CG_Mover
+===============
+*/
+static void CG_Mover( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ ent.renderfx = RF_NOSHADOW;
+
+ // flicker between two skins (FIXME?)
+ ent.skinNum = ( cg.time >> 6 ) & 1;
+
+ // get the model, either as a bmodel or a modelindex
+ if( s1->solid == SOLID_BMODEL )
+ ent.hModel = cgs.inlineDrawModel[ s1->modelindex ];
+ else
+ ent.hModel = cgs.gameModels[ s1->modelindex ];
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+
+ // add the secondary model
+ if( s1->modelindex2 )
+ {
+ ent.skinNum = 0;
+ ent.hModel = cgs.gameModels[ s1->modelindex2 ];
+ trap_R_AddRefEntityToScene( &ent );
+ }
+
+}
+
+/*
+===============
+CG_Beam
+
+Also called as an event
+===============
+*/
+void CG_Beam( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( s1->pos.trBase, ent.origin );
+ VectorCopy( s1->origin2, ent.oldorigin );
+ AxisClear( ent.axis );
+ ent.reType = RT_BEAM;
+
+ ent.renderfx = RF_NOSHADOW;
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+/*
+===============
+CG_Portal
+===============
+*/
+static void CG_Portal( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( s1->origin2, ent.oldorigin );
+ ByteToDir( s1->eventParm, ent.axis[ 0 ] );
+ PerpendicularVector( ent.axis[ 1 ], ent.axis[ 0 ] );
+
+ // negating this tends to get the directions like they want
+ // we really should have a camera roll value
+ VectorSubtract( vec3_origin, ent.axis[ 1 ], ent.axis[ 1 ] );
+
+ CrossProduct( ent.axis[ 0 ], ent.axis[ 1 ], ent.axis[ 2 ] );
+ ent.reType = RT_PORTALSURFACE;
+ ent.oldframe = s1->misc;
+ ent.frame = s1->frame; // rotation speed
+ ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+//============================================================================
+
+#define SETBOUNDS(v1,v2,r) ((v1)[0]=(-r/2),(v1)[1]=(-r/2),(v1)[2]=(-r/2),\
+ (v2)[0]=(r/2),(v2)[1]=(r/2),(v2)[2]=(r/2))
+#define RADIUSSTEP 0.5f
+
+#define FLARE_OFF 0
+#define FLARE_NOFADE 1
+#define FLARE_TIMEFADE 2
+#define FLARE_REALFADE 3
+
+/*
+=========================
+CG_LightFlare
+=========================
+*/
+static void CG_LightFlare( centity_t *cent )
+{
+ refEntity_t flare;
+ entityState_t *es;
+ vec3_t forward, delta;
+ float len;
+ trace_t tr;
+ float maxAngle;
+ vec3_t mins, maxs, start, end;
+ float srcRadius, srLocal, ratio = 1.0f;
+ int entityNum;
+
+ es = &cent->currentState;
+
+ if( cg.renderingThirdPerson )
+ entityNum = MAGIC_TRACE_HACK;
+ else
+ entityNum = cg.predictedPlayerState.clientNum;
+
+ //don't draw light flares
+ if( cg_lightFlare.integer == FLARE_OFF )
+ return;
+
+ //flare is "off"
+ if( es->eFlags & EF_NODRAW )
+ return;
+
+ CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, es->angles2,
+ entityNum, MASK_SHOT );
+
+ //if there is no los between the view and the flare source
+ //it definately cannot be seen
+ if( tr.fraction < 1.0f || tr.allsolid )
+ return;
+
+ memset( &flare, 0, sizeof( flare ) );
+
+ flare.reType = RT_SPRITE;
+ flare.customShader = cgs.gameShaders[ es->modelindex ];
+ flare.shaderRGBA[ 0 ] = 0xFF;
+ flare.shaderRGBA[ 1 ] = 0xFF;
+ flare.shaderRGBA[ 2 ] = 0xFF;
+ flare.shaderRGBA[ 3 ] = 0xFF;
+
+ //flares always drawn before the rest of the scene
+ flare.renderfx |= RF_DEPTHHACK;
+
+ //bunch of geometry
+ AngleVectors( es->angles, forward, NULL, NULL );
+ VectorCopy( cent->lerpOrigin, flare.origin );
+ VectorSubtract( flare.origin, cg.refdef.vieworg, delta );
+ len = VectorLength( delta );
+ VectorNormalize( delta );
+
+ //flare is too close to camera to be drawn
+ if( len < es->generic1 )
+ return;
+
+ //don't bother for flares behind the view plane
+ if( DotProduct( delta, cg.refdef.viewaxis[ 0 ] ) < 0.0 )
+ return;
+
+ //only recalculate radius and ratio every three frames
+ if( !( cg.clientFrame % 2 ) )
+ {
+ //can only see the flare when in front of it
+ flare.radius = len / es->origin2[ 0 ];
+
+ if( es->origin2[ 2 ] == 0 )
+ srcRadius = srLocal = flare.radius / 2.0f;
+ else
+ srcRadius = srLocal = len / es->origin2[ 2 ];
+
+ maxAngle = es->origin2[ 1 ];
+
+ if( maxAngle > 0.0f )
+ {
+ float radiusMod = 1.0f - ( 180.0f - RAD2DEG(
+ acos( DotProduct( delta, forward ) ) ) ) / maxAngle;
+
+ if( radiusMod < 0.0f )
+ radiusMod = 0.0f;
+
+ flare.radius *= radiusMod;
+ }
+
+ if( flare.radius < 0.0f )
+ flare.radius = 0.0f;
+
+ VectorMA( flare.origin, -flare.radius, delta, end );
+ VectorMA( cg.refdef.vieworg, flare.radius, delta, start );
+
+ if( cg_lightFlare.integer == FLARE_REALFADE )
+ {
+ //"correct" flares
+ CG_BiSphereTrace( &tr, cg.refdef.vieworg, end,
+ 1.0f, srcRadius, entityNum, MASK_SHOT );
+
+ if( tr.fraction < 1.0f )
+ ratio = tr.lateralFraction;
+ else
+ ratio = 1.0f;
+ }
+ else if( cg_lightFlare.integer == FLARE_TIMEFADE )
+ {
+ //draw timed flares
+ SETBOUNDS( mins, maxs, srcRadius );
+ CG_Trace( &tr, start, mins, maxs, end,
+ entityNum, MASK_SHOT );
+
+ if( ( tr.fraction < 1.0f || tr.startsolid ) && cent->lfs.status )
+ {
+ cent->lfs.status = qfalse;
+ cent->lfs.lastTime = cg.time;
+ }
+ else if( ( tr.fraction == 1.0f && !tr.startsolid ) && !cent->lfs.status )
+ {
+ cent->lfs.status = qtrue;
+ cent->lfs.lastTime = cg.time;
+ }
+
+ //fade flare up
+ if( cent->lfs.status )
+ {
+ if( cent->lfs.lastTime + es->time > cg.time )
+ ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time;
+ }
+
+ //fade flare down
+ if( !cent->lfs.status )
+ {
+ if( cent->lfs.lastTime + es->time > cg.time )
+ {
+ ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time;
+ ratio = 1.0f - ratio;
+ }
+ else
+ ratio = 0.0f;
+ }
+ }
+ else if( cg_lightFlare.integer == FLARE_NOFADE )
+ {
+ //draw nofade flares
+ SETBOUNDS( mins, maxs, srcRadius );
+ CG_Trace( &tr, start, mins, maxs, end,
+ entityNum, MASK_SHOT );
+
+ //flare source occluded
+ if( ( tr.fraction < 1.0f || tr.startsolid ) )
+ ratio = 0.0f;
+ }
+ }
+ else
+ {
+ ratio = cent->lfs.lastRatio;
+ flare.radius = cent->lfs.lastRadius;
+ }
+
+ cent->lfs.lastRatio = ratio;
+ cent->lfs.lastRadius = flare.radius;
+
+ if( ratio < 1.0f )
+ {
+ flare.radius *= ratio;
+ flare.shaderRGBA[ 3 ] = (byte)( (float)flare.shaderRGBA[ 3 ] * ratio );
+ }
+
+ if( flare.radius <= 0.0f )
+ return;
+
+ trap_R_AddRefEntityToScene( &flare );
+}
+
+/*
+=========================
+CG_Lev2ZapChain
+=========================
+*/
+static void CG_Lev2ZapChain( centity_t *cent )
+{
+ int i;
+ entityState_t *es;
+ centity_t *source = NULL, *target = NULL;
+
+ es = &cent->currentState;
+
+ for( i = 0; i <= 2; i++ )
+ {
+ switch( i )
+ {
+ case 0:
+ if( es->time <= 0 )
+ continue;
+
+ source = &cg_entities[ es->misc ];
+ target = &cg_entities[ es->time ];
+ break;
+
+ case 1:
+ if( es->time2 <= 0 )
+ continue;
+
+ source = &cg_entities[ es->time ];
+ target = &cg_entities[ es->time2 ];
+ break;
+
+ case 2:
+ if( es->constantLight <= 0 )
+ continue;
+
+ source = &cg_entities[ es->time2 ];
+ target = &cg_entities[ es->constantLight ];
+ break;
+ }
+
+ if( !CG_IsTrailSystemValid( &cent->level2ZapTS[ i ] ) )
+ cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS );
+
+ if( CG_IsTrailSystemValid( &cent->level2ZapTS[ i ] ) )
+ {
+ CG_SetAttachmentCent( &cent->level2ZapTS[ i ]->frontAttachment, source );
+ CG_SetAttachmentCent( &cent->level2ZapTS[ i ]->backAttachment, target );
+ CG_AttachToCent( &cent->level2ZapTS[ i ]->frontAttachment );
+ CG_AttachToCent( &cent->level2ZapTS[ i ]->backAttachment );
+ }
+ }
+}
+
+/*
+=========================
+CG_AdjustPositionForMover
+
+Also called by client movement prediction code
+=========================
+*/
+void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out )
+{
+ centity_t *cent;
+ vec3_t oldOrigin, origin, deltaOrigin;
+ vec3_t oldAngles, angles, deltaAngles;
+
+ if( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ cent = &cg_entities[ moverNum ];
+
+ if( cent->currentState.eType != ET_MOVER )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, fromTime, oldOrigin );
+ BG_EvaluateTrajectory( &cent->currentState.apos, fromTime, oldAngles );
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, toTime, origin );
+ BG_EvaluateTrajectory( &cent->currentState.apos, toTime, angles );
+
+ VectorSubtract( origin, oldOrigin, deltaOrigin );
+ VectorSubtract( angles, oldAngles, deltaAngles );
+
+ VectorAdd( in, deltaOrigin, out );
+
+ // FIXME: origin change when on a rotating object
+}
+
+
+/*
+=============================
+CG_InterpolateEntityPosition
+=============================
+*/
+static void CG_InterpolateEntityPosition( centity_t *cent )
+{
+ vec3_t current, next;
+ float f;
+
+ // it would be an internal error to find an entity that interpolates without
+ // a snapshot ahead of the current one
+ if( cg.nextSnap == NULL )
+ CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" );
+
+ f = cg.frameInterpolation;
+
+ // this will linearize a sine or parabolic curve, but it is important
+ // to not extrapolate player positions if more recent data is available
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, current );
+ BG_EvaluateTrajectory( &cent->nextState.pos, cg.nextSnap->serverTime, next );
+
+ cent->lerpOrigin[ 0 ] = current[ 0 ] + f * ( next[ 0 ] - current[ 0 ] );
+ cent->lerpOrigin[ 1 ] = current[ 1 ] + f * ( next[ 1 ] - current[ 1 ] );
+ cent->lerpOrigin[ 2 ] = current[ 2 ] + f * ( next[ 2 ] - current[ 2 ] );
+
+ BG_EvaluateTrajectory( &cent->currentState.apos, cg.snap->serverTime, current );
+ BG_EvaluateTrajectory( &cent->nextState.apos, cg.nextSnap->serverTime, next );
+
+ cent->lerpAngles[ 0 ] = LerpAngle( current[ 0 ], next[ 0 ], f );
+ cent->lerpAngles[ 1 ] = LerpAngle( current[ 1 ], next[ 1 ], f );
+ cent->lerpAngles[ 2 ] = LerpAngle( current[ 2 ], next[ 2 ], f );
+
+}
+
+/*
+===============
+CG_CalcEntityLerpPositions
+
+===============
+*/
+static void CG_CalcEntityLerpPositions( centity_t *cent )
+{
+ // this will be set to how far forward projectiles will be extrapolated
+ int timeshift = 0;
+
+ // if this player does not want to see extrapolated players
+ if( !cg_smoothClients.integer )
+ {
+ // make sure the clients use TR_INTERPOLATE
+ if( cent->currentState.number < MAX_CLIENTS )
+ {
+ cent->currentState.pos.trType = TR_INTERPOLATE;
+ cent->nextState.pos.trType = TR_INTERPOLATE;
+ }
+ }
+
+ if( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE )
+ {
+ CG_InterpolateEntityPosition( cent );
+ return;
+ }
+
+ // first see if we can interpolate between two snaps for
+ // linear extrapolated clients
+ if( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP &&
+ cent->currentState.number < MAX_CLIENTS )
+ {
+ CG_InterpolateEntityPosition( cent );
+ return;
+ }
+
+ if( cg_projectileNudge.integer > 0 &&
+ cent->currentState.eType == ET_MISSILE &&
+ !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ timeshift = cg.ping;
+ }
+
+ // just use the current frame and evaluate as best we can
+ BG_EvaluateTrajectory( &cent->currentState.pos,
+ ( cg.time + timeshift ), cent->lerpOrigin );
+ BG_EvaluateTrajectory( &cent->currentState.apos,
+ ( cg.time + timeshift ), cent->lerpAngles );
+
+ if( timeshift )
+ {
+ trace_t tr;
+ vec3_t lastOrigin;
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, lastOrigin );
+
+ CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin,
+ cent->currentState.number, MASK_SHOT );
+
+ // don't let the projectile go through the floor
+ if( tr.fraction < 1.0f )
+ VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin );
+ }
+
+ // adjust for riding a mover if it wasn't rolled into the predicted
+ // player state
+ if( cent != &cg.predictedPlayerEntity )
+ {
+ CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum,
+ cg.snap->serverTime, cg.time, cent->lerpOrigin );
+ }
+}
+
+
+/*
+===============
+CG_CEntityPVSEnter
+
+===============
+*/
+static void CG_CEntityPVSEnter( centity_t *cent )
+{
+ entityState_t *es = &cent->currentState;
+
+ if( cg_debugPVS.integer )
+ CG_Printf( "Entity %d entered PVS\n", cent->currentState.number );
+
+ switch( es->eType )
+ {
+ case ET_MISSILE:
+ CG_LaunchMissile( cent );
+ break;
+ }
+
+ //clear any particle systems from previous uses of this centity_t
+ cent->muzzlePS = NULL;
+ cent->muzzlePsTrigger = qfalse;
+ cent->jetPackPS = NULL;
+ cent->jetPackState = JPS_OFF;
+ cent->buildablePS = NULL;
+ cent->entityPS = NULL;
+ cent->entityPSMissing = qfalse;
+
+ //make sure that the buildable animations are in a consistent state
+ //when a buildable enters the PVS
+ cent->buildableAnim = cent->lerpFrame.animationNumber = BANIM_NONE;
+ cent->oldBuildableAnim = es->legsAnim;
+}
+
+
+/*
+===============
+CG_CEntityPVSLeave
+
+===============
+*/
+static void CG_CEntityPVSLeave( centity_t *cent )
+{
+ int i;
+ entityState_t *es = &cent->currentState;
+
+ if( cg_debugPVS.integer )
+ CG_Printf( "Entity %d left PVS\n", cent->currentState.number );
+
+ switch( es->eType )
+ {
+ case ET_LEV2_ZAP_CHAIN:
+ for( i = 0; i <= 2; i++ )
+ {
+ if( CG_IsTrailSystemValid( &cent->level2ZapTS[ i ] ) )
+ CG_DestroyTrailSystem( &cent->level2ZapTS[ i ] );
+ }
+ break;
+ }
+}
+
+
+/*
+===============
+CG_AddCEntity
+
+===============
+*/
+static void CG_AddCEntity( centity_t *cent )
+{
+ // event-only entities will have been dealt with already
+ if( cent->currentState.eType >= ET_EVENTS )
+ return;
+
+ // calculate the current origin
+ CG_CalcEntityLerpPositions( cent );
+
+ // add automatic effects
+ CG_EntityEffects( cent );
+
+ switch( cent->currentState.eType )
+ {
+ default:
+ CG_Error( "Bad entity type: %i\n", cent->currentState.eType );
+ break;
+
+ case ET_INVISIBLE:
+ case ET_PUSH_TRIGGER:
+ case ET_TELEPORT_TRIGGER:
+ break;
+
+ case ET_GENERAL:
+ CG_General( cent );
+ break;
+
+ case ET_CORPSE:
+ CG_Corpse( cent );
+ break;
+
+ case ET_PLAYER:
+ CG_Player( cent );
+ break;
+
+ case ET_BUILDABLE:
+ CG_Buildable( cent );
+ break;
+
+ case ET_MISSILE:
+ CG_Missile( cent );
+ break;
+
+ case ET_MOVER:
+ CG_Mover( cent );
+ break;
+
+ case ET_BEAM:
+ CG_Beam( cent );
+ break;
+
+ case ET_PORTAL:
+ CG_Portal( cent );
+ break;
+
+ case ET_SPEAKER:
+ CG_Speaker( cent );
+ break;
+
+ case ET_PARTICLE_SYSTEM:
+ CG_ParticleSystemEntity( cent );
+ break;
+
+ case ET_ANIMMAPOBJ:
+ CG_AnimMapObj( cent );
+ break;
+
+ case ET_MODELDOOR:
+ CG_ModelDoor( cent );
+ break;
+
+ case ET_LIGHTFLARE:
+ CG_LightFlare( cent );
+ break;
+
+ case ET_LEV2_ZAP_CHAIN:
+ CG_Lev2ZapChain( cent );
+ break;
+ }
+}
+
+/*
+===============
+CG_AddPacketEntities
+
+===============
+*/
+void CG_AddPacketEntities( void )
+{
+ int num;
+ centity_t *cent;
+ playerState_t *ps;
+
+ // set cg.frameInterpolation
+ if( cg.nextSnap )
+ {
+ int delta;
+
+ delta = ( cg.nextSnap->serverTime - cg.snap->serverTime );
+
+ if( delta == 0 )
+ cg.frameInterpolation = 0;
+ else
+ cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta;
+ }
+ else
+ {
+ cg.frameInterpolation = 0; // actually, it should never be used, because
+ // no entities should be marked as interpolating
+ }
+
+ // the auto-rotating items will all have the same axis
+ cg.autoAngles[ 0 ] = 0;
+ cg.autoAngles[ 1 ] = ( cg.time & 2047 ) * 360 / 2048.0;
+ cg.autoAngles[ 2 ] = 0;
+
+ cg.autoAnglesFast[ 0 ] = 0;
+ cg.autoAnglesFast[ 1 ] = ( cg.time & 1023 ) * 360 / 1024.0f;
+ cg.autoAnglesFast[ 2 ] = 0;
+
+ AnglesToAxis( cg.autoAngles, cg.autoAxis );
+ AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast );
+
+ // generate and add the entity from the playerstate
+ ps = &cg.predictedPlayerState;
+ BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse );
+ cg.predictedPlayerEntity.valid = qtrue;
+ CG_AddCEntity( &cg.predictedPlayerEntity );
+
+ // lerp the non-predicted value for lightning gun origins
+ CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );
+
+ // scanner
+ CG_UpdateEntityPositions( );
+
+ for( num = 0; num < MAX_GENTITIES; num++ )
+ cg_entities[ num ].valid = qfalse;
+
+ // add each entity sent over by the server
+ for( num = 0; num < cg.snap->numEntities; num++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ num ].number ];
+ cent->valid = qtrue;
+ }
+
+ for( num = 0; num < MAX_GENTITIES; num++ )
+ {
+ cent = &cg_entities[ num ];
+
+ if( cent->valid && !cent->oldValid )
+ CG_CEntityPVSEnter( cent );
+ else if( !cent->valid && cent->oldValid )
+ CG_CEntityPVSLeave( cent );
+
+ cent->oldValid = cent->valid;
+ }
+
+ // add each entity sent over by the server
+ for( num = 0; num < cg.snap->numEntities; num++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ num ].number ];
+ CG_AddCEntity( cent );
+ }
+
+ //make an attempt at drawing bounding boxes of selected entity types
+ if( cg_drawBBOX.integer )
+ {
+ for( num = 0; num < cg.snap->numEntities; num++ )
+ {
+ float x, zd, zu;
+ vec3_t mins, maxs;
+ entityState_t *es;
+
+ cent = &cg_entities[ cg.snap->entities[ num ].number ];
+ es = &cent->currentState;
+
+ switch( es->eType )
+ {
+ case ET_BUILDABLE:
+ case ET_MISSILE:
+ case ET_CORPSE:
+ x = ( es->solid & 255 );
+ zd = ( ( es->solid >> 8 ) & 255 );
+ zu = ( ( es->solid >> 16 ) & 255 ) - 32;
+
+ mins[ 0 ] = mins[ 1 ] = -x;
+ maxs[ 0 ] = maxs[ 1 ] = x;
+ mins[ 2 ] = -zd;
+ maxs[ 2 ] = zu;
+
+ CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs );
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c
new file mode 100644
index 0000000..07bcea9
--- /dev/null
+++ b/src/cgame/cg_event.c
@@ -0,0 +1,1034 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_event.c -- handle entity events at snapshot or playerstate transitions
+
+
+#include "cg_local.h"
+
+/*
+=============
+CG_Obituary
+=============
+*/
+static void CG_Obituary( entityState_t *ent )
+{
+ int mod;
+ int target, attacker;
+ char *message;
+ char *message2;
+ const char *targetInfo;
+ const char *attackerInfo;
+ char targetName[ 32 ];
+ char attackerName[ 32 ];
+ char className[ 64 ];
+ gender_t gender;
+ clientInfo_t *ci;
+ qboolean teamKill = qfalse;
+
+ target = ent->otherEntityNum;
+ attacker = ent->otherEntityNum2;
+ mod = ent->eventParm;
+
+ if( target < 0 || target >= MAX_CLIENTS )
+ CG_Error( "CG_Obituary: target out of range" );
+
+ ci = &cgs.clientinfo[ target ];
+
+ if( attacker < 0 || attacker >= MAX_CLIENTS )
+ {
+ attacker = ENTITYNUM_WORLD;
+ attackerInfo = NULL;
+ }
+ else
+ {
+ attackerInfo = CG_ConfigString( CS_PLAYERS + attacker );
+ if( ci && cgs.clientinfo[ attacker ].team == ci->team )
+ teamKill = qtrue;
+ }
+
+ targetInfo = CG_ConfigString( CS_PLAYERS + target );
+
+ if( !targetInfo )
+ return;
+
+ Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName ) - 2 );
+ strcat( targetName, S_COLOR_WHITE );
+
+ message2 = "";
+
+ // check for single client messages
+
+ switch( mod )
+ {
+ case MOD_SUICIDE:
+ message = "suicides";
+ break;
+ case MOD_FALLING:
+ message = "fell fowl to gravity";
+ break;
+ case MOD_CRUSH:
+ message = "was squished";
+ break;
+ case MOD_WATER:
+ message = "forgot to pack a snorkel";
+ break;
+ case MOD_SLIME:
+ message = "melted";
+ break;
+ case MOD_LAVA:
+ message = "does a back flip into the lava";
+ break;
+ case MOD_TARGET_LASER:
+ message = "saw the light";
+ break;
+ case MOD_TRIGGER_HURT:
+ message = "was in the wrong place";
+ break;
+ case MOD_HSPAWN:
+ message = "should have run further";
+ break;
+ case MOD_ASPAWN:
+ message = "shouldn't have trod in the acid";
+ break;
+ case MOD_MGTURRET:
+ message = "was gunned down by a turret";
+ break;
+ case MOD_TESLAGEN:
+ message = "was zapped by a tesla generator";
+ break;
+ case MOD_ATUBE:
+ message = "was melted by an acid tube";
+ break;
+ case MOD_OVERMIND:
+ message = "got too close to the overmind";
+ break;
+ case MOD_REACTOR:
+ message = "got too close to the reactor";
+ break;
+ case MOD_SLOWBLOB:
+ message = "should have visited a medical station";
+ break;
+ case MOD_SWARM:
+ message = "was hunted down by the swarm";
+ break;
+ default:
+ message = NULL;
+ break;
+ }
+
+ if( attacker == target )
+ {
+ gender = ci->gender;
+ switch( mod )
+ {
+ case MOD_FLAMER_SPLASH:
+ if( gender == GENDER_FEMALE )
+ message = "toasted herself";
+ else if( gender == GENDER_NEUTER )
+ message = "toasted itself";
+ else
+ message = "toasted himself";
+ break;
+
+ case MOD_LCANNON_SPLASH:
+ if( gender == GENDER_FEMALE )
+ message = "irradiated herself";
+ else if( gender == GENDER_NEUTER )
+ message = "irradiated itself";
+ else
+ message = "irradiated himself";
+ break;
+
+ case MOD_GRENADE:
+ if( gender == GENDER_FEMALE )
+ message = "blew herself up";
+ else if( gender == GENDER_NEUTER )
+ message = "blew itself up";
+ else
+ message = "blew himself up";
+ break;
+
+ default:
+ if( gender == GENDER_FEMALE )
+ message = "killed herself";
+ else if( gender == GENDER_NEUTER )
+ message = "killed itself";
+ else
+ message = "killed himself";
+ break;
+ }
+ }
+
+ if( message )
+ {
+ CG_Printf( "%s %s.\n", targetName, message );
+ return;
+ }
+
+ // check for double client messages
+ if( !attackerInfo )
+ {
+ attacker = ENTITYNUM_WORLD;
+ strcpy( attackerName, "noname" );
+ }
+ else
+ {
+ Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName ) - 2);
+ strcat( attackerName, S_COLOR_WHITE );
+ // check for kill messages about the current clientNum
+ if( target == cg.snap->ps.clientNum )
+ Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) );
+ }
+
+ if( attacker != ENTITYNUM_WORLD )
+ {
+ switch( mod )
+ {
+ case MOD_PAINSAW:
+ message = "was sawn by";
+ break;
+ case MOD_BLASTER:
+ message = "was blasted by";
+ break;
+ case MOD_MACHINEGUN:
+ message = "was machinegunned by";
+ break;
+ case MOD_CHAINGUN:
+ message = "was chaingunned by";
+ break;
+ case MOD_SHOTGUN:
+ message = "was gunned down by";
+ break;
+ case MOD_PRIFLE:
+ message = "was pulse rifled by";
+ break;
+ case MOD_MDRIVER:
+ message = "was mass driven by";
+ break;
+ case MOD_LASGUN:
+ message = "was lasgunned by";
+ break;
+ case MOD_FLAMER:
+ message = "was grilled by";
+ message2 = "'s flamer";
+ break;
+ case MOD_FLAMER_SPLASH:
+ message = "was toasted by";
+ message2 = "'s flamer";
+ break;
+ case MOD_LCANNON:
+ message = "felt the full force of";
+ message2 = "'s lucifer cannon";
+ break;
+ case MOD_LCANNON_SPLASH:
+ message = "was caught in the fallout of";
+ message2 = "'s lucifer cannon";
+ break;
+ case MOD_GRENADE:
+ message = "couldn't escape";
+ message2 = "'s grenade";
+ break;
+
+ case MOD_ABUILDER_CLAW:
+ message = "should leave";
+ message2 = "'s buildings alone";
+ break;
+ case MOD_LEVEL0_BITE:
+ message = "was bitten by";
+ break;
+ case MOD_LEVEL1_CLAW:
+ message = "was swiped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL2_CLAW:
+ message = "was clawed by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL2_ZAP:
+ message = "was zapped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL3_CLAW:
+ message = "was chomped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL3_POUNCE:
+ message = "was pounced upon by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL3_BOUNCEBALL:
+ message = "was sniped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL4_CLAW:
+ message = "was mauled by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL4_CHARGE:
+ message = "should have gotten out of the way of";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) );
+ message2 = className;
+ break;
+
+ case MOD_POISON:
+ message = "should have used a medkit against";
+ message2 = "'s poison";
+ break;
+ case MOD_LEVEL1_PCLOUD:
+ message = "was gassed by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) );
+ message2 = className;
+ break;
+
+
+ case MOD_TELEFRAG:
+ message = "tried to invade";
+ message2 = "'s personal space";
+ break;
+ default:
+ message = "was killed by";
+ break;
+ }
+
+ if( message )
+ {
+ CG_Printf( "%s %s %s%s%s\n",
+ targetName, message,
+ ( teamKill ) ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "",
+ attackerName, message2 );
+ if( teamKill && attacker == cg.clientNum )
+ {
+ CG_CenterPrint( va ( "You killed " S_COLOR_RED "TEAMMATE "
+ S_COLOR_WHITE "%s", targetName ),
+ SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
+ }
+ return;
+ }
+ }
+
+ // we don't know what it was
+ CG_Printf( "%s died.\n", targetName );
+}
+
+//==========================================================================
+
+/*
+================
+CG_PainEvent
+
+Also called by playerstate transition
+================
+*/
+void CG_PainEvent( centity_t *cent, int health )
+{
+ char *snd;
+
+ // don't do more than two pain sounds a second
+ if( cg.time - cent->pe.painTime < 500 )
+ return;
+
+ if( health < 25 )
+ snd = "*pain25_1.wav";
+ else if( health < 50 )
+ snd = "*pain50_1.wav";
+ else if( health < 75 )
+ snd = "*pain75_1.wav";
+ else
+ snd = "*pain100_1.wav";
+
+ trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE,
+ CG_CustomSound( cent->currentState.number, snd ) );
+
+ // save pain time for programitic twitch animation
+ cent->pe.painTime = cg.time;
+ cent->pe.painDirection ^= 1;
+}
+
+/*
+==============
+CG_EntityEvent
+
+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 )
+{
+ entityState_t *es;
+ int event;
+ vec3_t dir;
+ const char *s;
+ int clientNum;
+ clientInfo_t *ci;
+ int steptime;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ steptime = 200;
+ else
+ steptime = BG_FindSteptimeForClass( cg.snap->ps.stats[ STAT_PCLASS ] );
+
+ es = &cent->currentState;
+ event = es->event & ~EV_EVENT_BITS;
+
+ if( cg_debugEvents.integer )
+ CG_Printf( "ent:%3i event:%3i ", es->number, event );
+
+ if( !event )
+ {
+ DEBUGNAME("ZEROEVENT");
+ return;
+ }
+
+ clientNum = es->clientNum;
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
+ clientNum = 0;
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ switch( event )
+ {
+ //
+ // movement generated events
+ //
+ case EV_FOOTSTEP:
+ DEBUGNAME( "EV_FOOTSTEP" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ if( ci->footsteps == FOOTSTEP_CUSTOM )
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ ci->customFootsteps[ rand( ) & 3 ] );
+ else
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ ci->footsteps ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTSTEP_METAL:
+ DEBUGNAME( "EV_FOOTSTEP_METAL" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ if( ci->footsteps == FOOTSTEP_CUSTOM )
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ ci->customMetalFootsteps[ rand( ) & 3 ] );
+ else
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_METAL ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTSTEP_SQUELCH:
+ DEBUGNAME( "EV_FOOTSTEP_SQUELCH" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_FLESH ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTSPLASH:
+ DEBUGNAME( "EV_FOOTSPLASH" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTWADE:
+ DEBUGNAME( "EV_FOOTWADE" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_SWIM:
+ DEBUGNAME( "EV_SWIM" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ 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_FALLING:
+ DEBUGNAME( "EV_FALLING" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*falling1.wav" ) );
+ break;
+
+ case EV_STEP_4:
+ case EV_STEP_8:
+ case EV_STEP_12:
+ case EV_STEP_16: // smooth out step up transitions
+ case EV_STEPDN_4:
+ case EV_STEPDN_8:
+ case EV_STEPDN_12:
+ case EV_STEPDN_16: // smooth out step down transitions
+ DEBUGNAME( "EV_STEP" );
+ {
+ float oldStep;
+ int delta;
+ int step;
+
+ if( clientNum != cg.predictedPlayerState.clientNum )
+ break;
+
+ // if we are interpolating, we don't need to smooth steps
+ if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) ||
+ cg_nopredict.integer || cg_synchronousClients.integer )
+ break;
+
+ // check for stepping up before a previous step is completed
+ delta = cg.time - cg.stepTime;
+
+ if( delta < steptime )
+ oldStep = cg.stepChange * ( steptime - delta ) / steptime;
+ else
+ oldStep = 0;
+
+ // add this amount
+ if( event >= EV_STEPDN_4 )
+ {
+ step = 4 * ( event - EV_STEPDN_4 + 1 );
+ cg.stepChange = oldStep - step;
+ }
+ else
+ {
+ step = 4 * ( event - EV_STEP_4 + 1 );
+ cg.stepChange = oldStep + step;
+ }
+
+ if( cg.stepChange > MAX_STEP_CHANGE )
+ cg.stepChange = MAX_STEP_CHANGE;
+ else if( cg.stepChange < -MAX_STEP_CHANGE )
+ cg.stepChange = -MAX_STEP_CHANGE;
+
+ cg.stepTime = cg.time;
+ break;
+ }
+
+ case EV_JUMP:
+ DEBUGNAME( "EV_JUMP" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) );
+
+ if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ {
+ vec3_t surfNormal, refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t rotAxis;
+
+ if( clientNum != cg.predictedPlayerState.clientNum )
+ break;
+
+ //set surfNormal
+ VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal );
+
+ //if we are moving from one surface to another smooth the transition
+ if( !VectorCompare( surfNormal, cg.lastNormal ) && surfNormal[ 2 ] != 1.0f )
+ {
+ CrossProduct( refNormal, surfNormal, rotAxis );
+ VectorNormalize( rotAxis );
+
+ //add the op
+ CG_addSmoothOp( rotAxis, 15.0f, 1.0f );
+ }
+
+ //copy the current normal to the lastNormal
+ VectorCopy( surfNormal, cg.lastNormal );
+ }
+
+ break;
+
+ case EV_LEV1_GRAB:
+ DEBUGNAME( "EV_LEV1_GRAB" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL1Grab );
+ break;
+
+ case EV_LEV4_CHARGE_PREPARE:
+ DEBUGNAME( "EV_LEV4_CHARGE_PREPARE" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare );
+ break;
+
+ case EV_LEV4_CHARGE_START:
+ DEBUGNAME( "EV_LEV4_CHARGE_START" );
+ //FIXME: stop cgs.media.alienL4ChargePrepare playing here
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargeStart );
+ break;
+
+ case EV_TAUNT:
+ DEBUGNAME( "EV_TAUNT" );
+ if( !cg_noTaunt.integer )
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) );
+ break;
+
+ case EV_WATER_TOUCH:
+ DEBUGNAME( "EV_WATER_TOUCH" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound );
+ break;
+
+ case EV_WATER_LEAVE:
+ DEBUGNAME( "EV_WATER_LEAVE" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound );
+ break;
+
+ case EV_WATER_UNDER:
+ DEBUGNAME( "EV_WATER_UNDER" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound );
+ break;
+
+ case EV_WATER_CLEAR:
+ DEBUGNAME( "EV_WATER_CLEAR" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) );
+ break;
+
+ //
+ // weapon events
+ //
+ case EV_NOAMMO:
+ DEBUGNAME( "EV_NOAMMO" );
+ {
+ }
+ break;
+
+ case EV_CHANGE_WEAPON:
+ DEBUGNAME( "EV_CHANGE_WEAPON" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound );
+ break;
+
+ case EV_FIRE_WEAPON:
+ DEBUGNAME( "EV_FIRE_WEAPON" );
+ CG_FireWeapon( cent, WPM_PRIMARY );
+ break;
+
+ case EV_FIRE_WEAPON2:
+ DEBUGNAME( "EV_FIRE_WEAPON2" );
+ CG_FireWeapon( cent, WPM_SECONDARY );
+ break;
+
+ case EV_FIRE_WEAPON3:
+ DEBUGNAME( "EV_FIRE_WEAPON3" );
+ CG_FireWeapon( cent, WPM_TERTIARY );
+ break;
+
+ //=================================================================
+
+ //
+ // other events
+ //
+ case EV_PLAYER_TELEPORT_IN:
+ DEBUGNAME( "EV_PLAYER_TELEPORT_IN" );
+ //deprecated
+ break;
+
+ case EV_PLAYER_TELEPORT_OUT:
+ DEBUGNAME( "EV_PLAYER_TELEPORT_OUT" );
+ CG_PlayerDisconnect( position );
+ break;
+
+ case EV_BUILD_CONSTRUCT:
+ DEBUGNAME( "EV_BUILD_CONSTRUCT" );
+ //do something useful here
+ break;
+
+ case EV_BUILD_DESTROY:
+ DEBUGNAME( "EV_BUILD_DESTROY" );
+ //do something useful here
+ break;
+
+ case EV_RPTUSE_SOUND:
+ DEBUGNAME( "EV_RPTUSE_SOUND" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.repeaterUseSound );
+ break;
+
+ case EV_GRENADE_BOUNCE:
+ DEBUGNAME( "EV_GRENADE_BOUNCE" );
+ if( rand( ) & 1 )
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound1 );
+ else
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound2 );
+ break;
+
+ //
+ // missile impacts
+ //
+ case EV_MISSILE_HIT:
+ DEBUGNAME( "EV_MISSILE_HIT" );
+ ByteToDir( es->eventParm, dir );
+ CG_MissileHitPlayer( es->weapon, es->generic1, position, dir, es->otherEntityNum );
+ break;
+
+ case EV_MISSILE_MISS:
+ DEBUGNAME( "EV_MISSILE_MISS" );
+ ByteToDir( es->eventParm, dir );
+ CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT );
+ break;
+
+ case EV_MISSILE_MISS_METAL:
+ DEBUGNAME( "EV_MISSILE_MISS_METAL" );
+ ByteToDir( es->eventParm, dir );
+ CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL );
+ break;
+
+ case EV_HUMAN_BUILDABLE_EXPLOSION:
+ DEBUGNAME( "EV_HUMAN_BUILDABLE_EXPLOSION" );
+ ByteToDir( es->eventParm, dir );
+ CG_HumanBuildableExplosion( position, dir );
+ break;
+
+ case EV_ALIEN_BUILDABLE_EXPLOSION:
+ DEBUGNAME( "EV_ALIEN_BUILDABLE_EXPLOSION" );
+ ByteToDir( es->eventParm, dir );
+ CG_AlienBuildableExplosion( position, dir );
+ break;
+
+ case EV_TESLATRAIL:
+ DEBUGNAME( "EV_TESLATRAIL" );
+ cent->currentState.weapon = WP_TESLAGEN;
+ {
+ centity_t *source = &cg_entities[ es->generic1 ];
+ centity_t *target = &cg_entities[ es->clientNum ];
+ vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f };
+
+ if( !CG_IsTrailSystemValid( &source->muzzleTS ) )
+ {
+ source->muzzleTS = CG_SpawnNewTrailSystem( cgs.media.teslaZapTS );
+
+ if( CG_IsTrailSystemValid( &source->muzzleTS ) )
+ {
+ CG_SetAttachmentCent( &source->muzzleTS->frontAttachment, source );
+ CG_SetAttachmentCent( &source->muzzleTS->backAttachment, target );
+ CG_AttachToCent( &source->muzzleTS->frontAttachment );
+ CG_AttachToCent( &source->muzzleTS->backAttachment );
+ CG_SetAttachmentOffset( &source->muzzleTS->frontAttachment, sourceOffset );
+
+ source->muzzleTSDeathTime = cg.time + cg_teslaTrailTime.integer;
+ }
+ }
+ }
+ 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" );
+ if( cgs.gameSounds[ es->eventParm ] )
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] );
+ else
+ {
+ s = CG_ConfigString( CS_SOUNDS + es->eventParm );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) );
+ }
+ break;
+
+ case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes
+ DEBUGNAME( "EV_GLOBAL_SOUND" );
+ if( cgs.gameSounds[ es->eventParm ] )
+ trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] );
+ else
+ {
+ s = CG_ConfigString( CS_SOUNDS + es->eventParm );
+ trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) );
+ }
+ break;
+
+ case EV_PAIN:
+ // local player sounds are triggered in CG_CheckLocalSounds,
+ // so ignore events on the player
+ DEBUGNAME( "EV_PAIN" );
+ if( cent->currentState.number != cg.snap->ps.clientNum )
+ CG_PainEvent( cent, es->eventParm );
+ break;
+
+ case EV_DEATH1:
+ case EV_DEATH2:
+ case EV_DEATH3:
+ DEBUGNAME( "EV_DEATHx" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE,
+ CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) );
+ break;
+
+ case EV_OBITUARY:
+ DEBUGNAME( "EV_OBITUARY" );
+ CG_Obituary( es );
+ break;
+
+ case EV_GIB_PLAYER:
+ DEBUGNAME( "EV_GIB_PLAYER" );
+ // no gibbing
+ break;
+
+ case EV_STOPLOOPINGSOUND:
+ DEBUGNAME( "EV_STOPLOOPINGSOUND" );
+ trap_S_StopLoopingSound( es->number );
+ es->loopSound = 0;
+ break;
+
+ case EV_DEBUG_LINE:
+ DEBUGNAME( "EV_DEBUG_LINE" );
+ CG_Beam( cent );
+ break;
+
+ case EV_BUILD_DELAY:
+ DEBUGNAME( "EV_BUILD_DELAY" );
+ if( clientNum == cg.predictedPlayerState.clientNum )
+ {
+ trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND );
+ cg.lastBuildAttempt = cg.time;
+ }
+ break;
+
+ case EV_BUILD_REPAIR:
+ DEBUGNAME( "EV_BUILD_REPAIR" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairSound );
+ break;
+
+ case EV_BUILD_REPAIRED:
+ DEBUGNAME( "EV_BUILD_REPAIRED" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairedSound );
+ break;
+
+ case EV_OVERMIND_ATTACK:
+ DEBUGNAME( "EV_OVERMIND_ATTACK" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER );
+ CG_CenterPrint( "The Overmind is under attack!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_OVERMIND_DYING:
+ DEBUGNAME( "EV_OVERMIND_DYING" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER );
+ CG_CenterPrint( "The Overmind is dying!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_DCC_ATTACK:
+ DEBUGNAME( "EV_DCC_ATTACK" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //trap_S_StartLocalSound( cgs.media.humanDCCAttack, CHAN_ANNOUNCER );
+ CG_CenterPrint( "Our base is under attack!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_OVERMIND_SPAWNS:
+ DEBUGNAME( "EV_OVERMIND_SPAWNS" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER );
+ CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_ALIEN_EVOLVE:
+ DEBUGNAME( "EV_ALIEN_EVOLVE" );
+ trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound );
+ {
+ particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentCent( &ps->attachment, cent );
+ CG_AttachToCent( &ps->attachment );
+ }
+ }
+
+ if( es->number == cg.clientNum )
+ {
+ CG_ResetPainBlend( );
+ cg.spawnTime = cg.time;
+ }
+ break;
+
+ case EV_ALIEN_EVOLVE_FAILED:
+ DEBUGNAME( "EV_ALIEN_EVOLVE_FAILED" );
+ if( clientNum == cg.predictedPlayerState.clientNum )
+ {
+ //FIXME: change to "negative" sound
+ trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND );
+ cg.lastEvolveAttempt = cg.time;
+ }
+ break;
+
+ case EV_ALIEN_ACIDTUBE:
+ DEBUGNAME( "EV_ALIEN_ACIDTUBE" );
+ {
+ particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentCent( &ps->attachment, cent );
+ ByteToDir( es->eventParm, dir );
+ CG_SetParticleSystemNormal( ps, dir );
+ CG_AttachToCent( &ps->attachment );
+ }
+ }
+ break;
+
+ case EV_MEDKIT_USED:
+ DEBUGNAME( "EV_MEDKIT_USED" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.medkitUseSound );
+ break;
+
+ case EV_PLAYER_RESPAWN:
+ DEBUGNAME( "EV_PLAYER_RESPAWN" );
+ if( es->number == cg.clientNum )
+ cg.spawnTime = cg.time;
+ break;
+
+ default:
+ DEBUGNAME( "UNKNOWN" );
+ CG_Error( "Unknown event: %i", event );
+ break;
+ }
+}
+
+
+/*
+==============
+CG_CheckEvents
+
+==============
+*/
+void CG_CheckEvents( centity_t *cent )
+{
+ entity_event_t event;
+ entity_event_t oldEvent = EV_NONE;
+
+ // check for event-only entities
+ if( cent->currentState.eType > ET_EVENTS )
+ {
+ event = cent->currentState.eType - ET_EVENTS;
+
+ if( cent->previousEvent )
+ return; // already fired
+
+ cent->previousEvent = 1;
+
+ cent->currentState.event = cent->currentState.eType - ET_EVENTS;
+
+ // Move the pointer to the entity that the
+ // event was originally attached to
+ if( cent->currentState.eFlags & EF_PLAYER_EVENT )
+ {
+ cent = &cg_entities[ cent->currentState.otherEntityNum ];
+ oldEvent = cent->currentState.event;
+ cent->currentState.event = event;
+ }
+ }
+ else
+ {
+ // check for events riding with another entity
+ if( cent->currentState.event == cent->previousEvent )
+ return;
+
+ cent->previousEvent = cent->currentState.event;
+ if( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 )
+ return;
+ }
+
+ // calculate the position at exactly the frame time
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, cent->lerpOrigin );
+ CG_SetEntitySoundPosition( cent );
+
+ CG_EntityEvent( cent, cent->lerpOrigin );
+
+ // If this was a reattached spilled event, restore the original event
+ if( oldEvent != EV_NONE )
+ cent->currentState.event = oldEvent;
+}
+
diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h
new file mode 100644
index 0000000..ea1694b
--- /dev/null
+++ b/src/cgame/cg_local.h
@@ -0,0 +1,2207 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "../game/bg_public.h"
+#include "cg_public.h"
+#include "../ui/ui_shared.h"
+
+// The entire cgame module is unloaded and reloaded on each level change,
+// so there is NO persistant data between levels on the client side.
+// If you absolutely need something stored, it can either be kept
+// by the server in the server stored userinfos, or stashed in a cvar.
+
+#define CG_FONT_THRESHOLD 0.1
+
+#define POWERUP_BLINKS 5
+
+#define POWERUP_BLINK_TIME 1000
+#define FADE_TIME 200
+#define PULSE_TIME 200
+#define DAMAGE_DEFLECT_TIME 100
+#define DAMAGE_RETURN_TIME 400
+#define DAMAGE_TIME 500
+#define LAND_DEFLECT_TIME 150
+#define LAND_RETURN_TIME 300
+#define DUCK_TIME 100
+#define PAIN_TWITCH_TIME 200
+#define WEAPON_SELECT_TIME 1400
+#define ITEM_SCALEUP_TIME 1000
+#define ZOOM_TIME 150
+#define ITEM_BLOB_TIME 200
+#define MUZZLE_FLASH_TIME 20
+#define SINK_TIME 1000 // time for fragments to sink into ground before going away
+#define ATTACKER_HEAD_TIME 10000
+#define REWARD_TIME 3000
+
+#define PULSE_SCALE 1.5 // amount to scale up the icons when activating
+
+#define MAX_STEP_CHANGE 32
+
+#define MAX_VERTS_ON_POLY 10
+#define MAX_MARK_POLYS 256
+
+#define STAT_MINUS 10 // num frame for '-' stats digit
+
+#define ICON_SIZE 48
+#define CHAR_WIDTH 32
+#define CHAR_HEIGHT 48
+#define TEXT_ICON_SPACE 4
+
+// very large characters
+#define GIANT_WIDTH 32
+#define GIANT_HEIGHT 48
+
+#define NUM_CROSSHAIRS 10
+
+#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"
+
+typedef enum
+{
+ FOOTSTEP_NORMAL,
+ FOOTSTEP_FLESH,
+ FOOTSTEP_METAL,
+ FOOTSTEP_SPLASH,
+ FOOTSTEP_CUSTOM,
+ FOOTSTEP_NONE,
+
+ FOOTSTEP_TOTAL
+} footstep_t;
+
+typedef enum
+{
+ IMPACTSOUND_DEFAULT,
+ IMPACTSOUND_METAL,
+ IMPACTSOUND_FLESH
+} impactSound_t;
+
+typedef enum
+{
+ JPS_OFF,
+ JPS_DESCENDING,
+ JPS_HOVERING,
+ JPS_ASCENDING
+} jetPackState_t;
+
+//======================================================================
+
+// when changing animation, set animationTime to frameTime + lerping time
+// The current lerp will finish out, then it will lerp to the new animation
+typedef struct
+{
+ int oldFrame;
+ int oldFrameTime; // time when ->oldFrame was exactly on
+
+ int frame;
+ int frameTime; // time when ->frame will be exactly on
+
+ float backlerp;
+
+ float yawAngle;
+ qboolean yawing;
+ float pitchAngle;
+ qboolean pitching;
+
+ int animationNumber; // may include ANIM_TOGGLEBIT
+ animation_t *animation;
+ int animationTime; // time when the first frame of the animation will be exact
+} lerpFrame_t;
+
+//======================================================================
+
+//attachment system
+typedef enum
+{
+ AT_STATIC,
+ AT_TAG,
+ AT_CENT,
+ AT_PARTICLE
+} attachmentType_t;
+
+//forward declaration for particle_t
+struct particle_s;
+
+typedef struct attachment_s
+{
+ attachmentType_t type;
+ qboolean attached;
+
+ qboolean staticValid;
+ qboolean tagValid;
+ qboolean centValid;
+ qboolean particleValid;
+
+ qboolean hasOffset;
+ vec3_t offset;
+
+ vec3_t lastValidAttachmentPoint;
+
+ //AT_STATIC
+ vec3_t origin;
+
+ //AT_TAG
+ refEntity_t re; //FIXME: should be pointers?
+ refEntity_t parent; //
+ qhandle_t model;
+ char tagName[ MAX_STRING_CHARS ];
+
+ //AT_CENT
+ int centNum;
+
+ //AT_PARTICLE
+ struct particle_s *particle;
+} attachment_t;
+
+//======================================================================
+
+//particle system stuff
+#define MAX_PARTICLE_FILES 128
+
+#define MAX_PS_SHADER_FRAMES 32
+#define MAX_PS_MODELS 8
+#define MAX_EJECTORS_PER_SYSTEM 4
+#define MAX_PARTICLES_PER_EJECTOR 4
+
+#define MAX_BASEPARTICLE_SYSTEMS 192
+#define MAX_BASEPARTICLE_EJECTORS MAX_BASEPARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM
+#define MAX_BASEPARTICLES MAX_BASEPARTICLE_EJECTORS*MAX_PARTICLES_PER_EJECTOR
+
+#define MAX_PARTICLE_SYSTEMS 48
+#define MAX_PARTICLE_EJECTORS MAX_PARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM
+#define MAX_PARTICLES MAX_PARTICLE_EJECTORS*5
+
+#define PARTICLES_INFINITE -1
+#define PARTICLES_SAME_AS_INITIAL -2
+
+//COMPILE TIME STRUCTURES
+typedef enum
+{
+ PMT_STATIC,
+ PMT_STATIC_TRANSFORM,
+ PMT_TAG,
+ PMT_CENT_ANGLES,
+ PMT_NORMAL
+} pMoveType_t;
+
+typedef enum
+{
+ PMD_LINEAR,
+ PMD_POINT
+} pDirType_t;
+
+typedef struct pMoveValues_u
+{
+ pDirType_t dirType;
+
+ //PMD_LINEAR
+ vec3_t dir;
+ float dirRandAngle;
+
+ //PMD_POINT
+ vec3_t point;
+ float pointRandAngle;
+
+ float mag;
+ float magRandFrac;
+
+ float parentVelFrac;
+ float parentVelFracRandFrac;
+} pMoveValues_t;
+
+typedef struct pLerpValues_s
+{
+ int delay;
+ float delayRandFrac;
+
+ float initial;
+ float initialRandFrac;
+
+ float final;
+ float finalRandFrac;
+
+ float randFrac;
+} pLerpValues_t;
+
+//particle template
+typedef struct baseParticle_s
+{
+ vec3_t displacement;
+ float randDisplacement;
+ float normalDisplacement;
+
+ pMoveType_t velMoveType;
+ pMoveValues_t velMoveValues;
+
+ pMoveType_t accMoveType;
+ pMoveValues_t accMoveValues;
+
+ int lifeTime;
+ float lifeTimeRandFrac;
+
+ float bounceFrac;
+ float bounceFracRandFrac;
+ qboolean bounceCull;
+
+ char bounceMarkName[ MAX_QPATH ];
+ qhandle_t bounceMark;
+ float bounceMarkRadius;
+ float bounceMarkRadiusRandFrac;
+ float bounceMarkCount;
+ float bounceMarkCountRandFrac;
+
+ char bounceSoundName[ MAX_QPATH ];
+ qhandle_t bounceSound;
+ float bounceSoundCount;
+ float bounceSoundCountRandFrac;
+
+ pLerpValues_t radius;
+ pLerpValues_t alpha;
+ pLerpValues_t rotation;
+
+ qboolean dynamicLight;
+ pLerpValues_t dLightRadius;
+ byte dLightColor[ 3 ];
+
+ int colorDelay;
+ float colorDelayRandFrac;
+ byte initialColor[ 3 ];
+ byte finalColor[ 3 ];
+
+ char childSystemName[ MAX_QPATH ];
+ qhandle_t childSystemHandle;
+
+ char onDeathSystemName[ MAX_QPATH ];
+ qhandle_t onDeathSystemHandle;
+
+ char childTrailSystemName[ MAX_QPATH ];
+ qhandle_t childTrailSystemHandle;
+
+ //particle invariant stuff
+ char shaderNames[ MAX_PS_SHADER_FRAMES ][ MAX_QPATH ];
+ qhandle_t shaders[ MAX_PS_SHADER_FRAMES ];
+ int numFrames;
+ float framerate;
+
+ char modelNames[ MAX_PS_MODELS ][ MAX_QPATH ];
+ qhandle_t models[ MAX_PS_MODELS ];
+ int numModels;
+ animation_t modelAnimation;
+
+ qboolean overdrawProtection;
+ qboolean realLight;
+ qboolean cullOnStartSolid;
+} baseParticle_t;
+
+
+//ejector template
+typedef struct baseParticleEjector_s
+{
+ baseParticle_t *particles[ MAX_PARTICLES_PER_EJECTOR ];
+ int numParticles;
+
+ pLerpValues_t eject; //zero period indicates creation of all particles at once
+
+ int totalParticles; //can be infinite
+ float totalParticlesRandFrac;
+} baseParticleEjector_t;
+
+
+//particle system template
+typedef struct baseParticleSystem_s
+{
+ char name[ MAX_QPATH ];
+ baseParticleEjector_t *ejectors[ MAX_EJECTORS_PER_SYSTEM ];
+ int numEjectors;
+
+ qboolean thirdPersonOnly;
+ qboolean registered; //whether or not the assets for this particle have been loaded
+} baseParticleSystem_t;
+
+
+//RUN TIME STRUCTURES
+typedef struct particleSystem_s
+{
+ baseParticleSystem_t *class;
+
+ attachment_t attachment;
+
+ qboolean valid;
+ qboolean lazyRemove; //mark this system for later removal
+
+ //for PMT_NORMAL
+ qboolean normalValid;
+ vec3_t normal;
+} particleSystem_t;
+
+
+typedef struct particleEjector_s
+{
+ baseParticleEjector_t *class;
+ particleSystem_t *parent;
+
+ pLerpValues_t ejectPeriod;
+
+ int count;
+ int totalParticles;
+
+ int nextEjectionTime;
+
+ qboolean valid;
+} particleEjector_t;
+
+
+//used for actual particle evaluation
+typedef struct particle_s
+{
+ baseParticle_t *class;
+ particleEjector_t *parent;
+
+ int birthTime;
+ int lifeTime;
+
+ float bounceMarkRadius;
+ int bounceMarkCount;
+ int bounceSoundCount;
+ qboolean atRest;
+
+ vec3_t origin;
+ vec3_t velocity;
+
+ pMoveType_t accMoveType;
+ pMoveValues_t accMoveValues;
+
+ int lastEvalTime;
+
+ int nextChildTime;
+
+ pLerpValues_t radius;
+ pLerpValues_t alpha;
+ pLerpValues_t rotation;
+
+ pLerpValues_t dLightRadius;
+
+ int colorDelay;
+
+ qhandle_t model;
+ lerpFrame_t lf;
+ vec3_t lastAxis[ 3 ];
+
+ qboolean valid;
+ int frameWhenInvalidated;
+
+ int sortKey;
+} particle_t;
+
+//======================================================================
+
+//trail system stuff
+#define MAX_TRAIL_FILES 128
+
+#define MAX_BEAMS_PER_SYSTEM 4
+
+#define MAX_BASETRAIL_SYSTEMS 64
+#define MAX_BASETRAIL_BEAMS MAX_BASETRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM
+
+#define MAX_TRAIL_SYSTEMS 32
+#define MAX_TRAIL_BEAMS MAX_TRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM
+#define MAX_TRAIL_BEAM_NODES 128
+
+#define MAX_TRAIL_BEAM_JITTERS 4
+
+typedef enum
+{
+ TBTT_STRETCH,
+ TBTT_REPEAT
+} trailBeamTextureType_t;
+
+typedef struct baseTrailJitter_s
+{
+ float magnitude;
+ int period;
+} baseTrailJitter_t;
+
+//beam template
+typedef struct baseTrailBeam_s
+{
+ int numSegments;
+ float frontWidth;
+ float backWidth;
+ float frontAlpha;
+ float backAlpha;
+ byte frontColor[ 3 ];
+ byte backColor[ 3 ];
+
+ // the time it takes for a segment to vanish (single attached only)
+ int segmentTime;
+
+ // the time it takes for a beam to fade out (double attached only)
+ int fadeOutTime;
+
+ char shaderName[ MAX_QPATH ];
+ qhandle_t shader;
+
+ trailBeamTextureType_t textureType;
+
+ //TBTT_STRETCH
+ float frontTextureCoord;
+ float backTextureCoord;
+
+ //TBTT_REPEAT
+ float repeatLength;
+ qboolean clampToBack;
+
+ qboolean realLight;
+
+ int numJitters;
+ baseTrailJitter_t jitters[ MAX_TRAIL_BEAM_JITTERS ];
+ qboolean jitterAttachments;
+} baseTrailBeam_t;
+
+
+//trail system template
+typedef struct baseTrailSystem_s
+{
+ char name[ MAX_QPATH ];
+ baseTrailBeam_t *beams[ MAX_BEAMS_PER_SYSTEM ];
+ int numBeams;
+
+ qboolean thirdPersonOnly;
+ qboolean registered; //whether or not the assets for this trail have been loaded
+} baseTrailSystem_t;
+
+typedef struct trailSystem_s
+{
+ baseTrailSystem_t *class;
+
+ attachment_t frontAttachment;
+ attachment_t backAttachment;
+
+ int destroyTime;
+ qboolean valid;
+} trailSystem_t;
+
+typedef struct trailBeamNode_s
+{
+ vec3_t refPosition;
+ vec3_t position;
+
+ int timeLeft;
+
+ float textureCoord;
+ float halfWidth;
+ byte alpha;
+ byte color[ 3 ];
+
+ vec2_t jitters[ MAX_TRAIL_BEAM_JITTERS ];
+
+ struct trailBeamNode_s *prev;
+ struct trailBeamNode_s *next;
+
+ qboolean used;
+} trailBeamNode_t;
+
+typedef struct trailBeam_s
+{
+ baseTrailBeam_t *class;
+ trailSystem_t *parent;
+
+ trailBeamNode_t nodePool[ MAX_TRAIL_BEAM_NODES ];
+ trailBeamNode_t *nodes;
+
+ int lastEvalTime;
+
+ qboolean valid;
+
+ int nextJitterTimes[ MAX_TRAIL_BEAM_JITTERS ];
+} trailBeam_t;
+
+//======================================================================
+
+// player entities need to track more information
+// than any other type of entity.
+
+// note that not every player entity is a client entity,
+// because corpses after respawn are outside the normal
+// client numbering range
+
+//TA: smoothing of view and model for WW transitions
+#define MAXSMOOTHS 32
+
+typedef struct
+{
+ float time;
+ float timeMod;
+
+ vec3_t rotAxis;
+ float rotAngle;
+} smooth_t;
+
+
+typedef struct
+{
+ lerpFrame_t legs, torso, flag, nonseg;
+ int painTime;
+ int painDirection; // flip from 0 to 1
+
+ // machinegun spinning
+ float barrelAngle;
+ int barrelTime;
+ qboolean barrelSpinning;
+
+ vec3_t lastNormal;
+ vec3_t lastAxis[ 3 ];
+ smooth_t sList[ MAXSMOOTHS ];
+} playerEntity_t;
+
+typedef struct lightFlareStatus_s
+{
+ float lastRadius; //caching of likely flare radius
+ float lastRatio; //caching of likely flare ratio
+ int lastTime; //last time flare was visible/occluded
+ qboolean status; //flare is visble?
+} lightFlareStatus_t;
+
+typedef struct buildableStatus_s
+{
+ int lastTime; // Last time status was visible
+ qboolean visible; // Status is visble?
+} buildableStatus_t;
+
+//=================================================
+
+// centity_t have a direct corespondence with gentity_t in the game, but
+// only the entityState_t is directly communicated to the cgame
+typedef struct centity_s
+{
+ entityState_t currentState; // from cg.frame
+ entityState_t nextState; // from cg.nextFrame, if available
+ qboolean interpolate; // true if next is valid to interpolate to
+ qboolean currentValid; // true if cg.frame holds this entity
+
+ int muzzleFlashTime; // move to playerEntity?
+ int muzzleFlashTime2; // move to playerEntity?
+ int muzzleFlashTime3; // move to playerEntity?
+ int previousEvent;
+ int teleportFlag;
+
+ int trailTime; // so missile trails can handle dropped initial packets
+ int dustTrailTime;
+ int miscTime;
+ int snapShotTime; // last time this entity was found in a snapshot
+
+ playerEntity_t pe;
+
+ int errorTime; // decay the error from this time
+ vec3_t errorOrigin;
+ vec3_t errorAngles;
+
+ qboolean extrapolated; // false if origin / angles is an interpolation
+ vec3_t rawOrigin;
+ vec3_t rawAngles;
+
+ vec3_t beamEnd;
+
+ // exact interpolated position of entity on this frame
+ vec3_t lerpOrigin;
+ vec3_t lerpAngles;
+
+ lerpFrame_t lerpFrame;
+
+ //TA:
+ buildableAnimNumber_t buildableAnim; //persistant anim number
+ buildableAnimNumber_t oldBuildableAnim; //to detect when new anims are set
+ particleSystem_t *buildablePS;
+ buildableStatus_t buildableStatus;
+ float lastBuildableHealthScale;
+ int lastBuildableDamageSoundTime;
+
+ lightFlareStatus_t lfs;
+
+ qboolean doorState;
+
+ qboolean animInit;
+ qboolean animPlaying;
+ qboolean animLastState;
+
+ particleSystem_t *muzzlePS;
+ qboolean muzzlePsTrigger;
+
+ particleSystem_t *jetPackPS;
+ jetPackState_t jetPackState;
+
+ particleSystem_t *entityPS;
+ qboolean entityPSMissing;
+
+ trailSystem_t *level2ZapTS[ 3 ];
+
+ trailSystem_t *muzzleTS; //used for the tesla and reactor
+ int muzzleTSDeathTime;
+
+ qboolean valid;
+ qboolean oldValid;
+} centity_t;
+
+
+//======================================================================
+
+typedef struct markPoly_s
+{
+ struct markPoly_s *prevMark, *nextMark;
+ int time;
+ qhandle_t markShader;
+ qboolean alphaFade; // fade alpha instead of rgb
+ float color[ 4 ];
+ poly_t poly;
+ polyVert_t verts[ MAX_VERTS_ON_POLY ];
+} markPoly_t;
+
+//======================================================================
+
+
+typedef struct
+{
+ int client;
+ int score;
+ int ping;
+ int time;
+ int team;
+ weapon_t weapon;
+ upgrade_t upgrade;
+} score_t;
+
+// each client has an associated clientInfo_t
+// that contains media references necessary to present the
+// client model and other color coded effects
+// this is regenerated each time a client's configstring changes,
+// usually as a result of a userinfo (name, model, etc) change
+#define MAX_CUSTOM_SOUNDS 32
+typedef struct
+{
+ qboolean infoValid;
+
+ char name[ MAX_QPATH ];
+ pTeam_t team;
+
+ int botSkill; // 0 = not bot, 1-5 = bot
+
+ vec3_t color1;
+ vec3_t color2;
+
+ int score; // updated by score servercmds
+ int location; // location index for team mode
+ int health; // you only get this info about your teammates
+ int armor;
+ int curWeapon;
+
+ int handicap;
+ int wins, losses; // in tourney mode
+
+ int teamTask; // task in teamplay (offence/defence)
+ qboolean teamLeader; // true when this is a team leader
+
+ int powerups; // so can display quad/flag status
+
+ int medkitUsageTime;
+ int invulnerabilityStartTime;
+ int invulnerabilityStopTime;
+
+ int breathPuffTime;
+
+ // when clientinfo is changed, the loading of models/skins/sounds
+ // can be deferred until you are dead, to prevent hitches in
+ // gameplay
+ char modelName[ MAX_QPATH ];
+ char skinName[ MAX_QPATH ];
+ char headModelName[ MAX_QPATH ];
+ char headSkinName[ MAX_QPATH ];
+
+ qboolean newAnims; // true if using the new mission pack animations
+ qboolean fixedlegs; // true if legs yaw is always the same as torso yaw
+ qboolean fixedtorso; // true if torso never changes yaw
+ qboolean nonsegmented; // true if model is Q2 style nonsegmented
+
+ vec3_t headOffset; // move head in icon views
+ footstep_t footsteps;
+ gender_t gender; // from model
+
+ qhandle_t legsModel;
+ qhandle_t legsSkin;
+
+ qhandle_t torsoModel;
+ qhandle_t torsoSkin;
+
+ qhandle_t headModel;
+ qhandle_t headSkin;
+
+ qhandle_t nonSegModel; //non-segmented model system
+ qhandle_t nonSegSkin; //non-segmented model system
+
+ qhandle_t modelIcon;
+
+ animation_t animations[ MAX_PLAYER_TOTALANIMATIONS ];
+
+ sfxHandle_t sounds[ MAX_CUSTOM_SOUNDS ];
+
+ sfxHandle_t customFootsteps[ 4 ];
+ sfxHandle_t customMetalFootsteps[ 4 ];
+} clientInfo_t;
+
+
+typedef struct weaponInfoMode_s
+{
+ float flashDlight;
+ vec3_t flashDlightColor;
+ sfxHandle_t flashSound[ 4 ]; // fast firing weapons randomly choose
+ qboolean continuousFlash;
+
+ qhandle_t missileModel;
+ sfxHandle_t missileSound;
+ float missileDlight;
+ vec3_t missileDlightColor;
+ int missileRenderfx;
+ qboolean usesSpriteMissle;
+ qhandle_t missileSprite;
+ int missileSpriteSize;
+ qhandle_t missileParticleSystem;
+ qhandle_t missileTrailSystem;
+ qboolean missileRotates;
+ qboolean missileAnimates;
+ int missileAnimStartFrame;
+ int missileAnimNumFrames;
+ int missileAnimFrameRate;
+ int missileAnimLooping;
+
+ sfxHandle_t firingSound;
+ qboolean loopFireSound;
+
+ qhandle_t muzzleParticleSystem;
+
+ qboolean alwaysImpact;
+ qhandle_t impactParticleSystem;
+ qhandle_t impactMark;
+ qhandle_t impactMarkSize;
+ sfxHandle_t impactSound[ 4 ]; //random impact sound
+ sfxHandle_t impactFleshSound[ 4 ]; //random impact sound
+} weaponInfoMode_t;
+
+// each WP_* weapon enum has an associated weaponInfo_t
+// that contains media references necessary to present the
+// weapon and its effects
+typedef struct weaponInfo_s
+{
+ qboolean registered;
+ char *humanName;
+
+ 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
+
+ qhandle_t weaponIcon;
+ qhandle_t ammoIcon;
+
+ qhandle_t crossHair;
+ int crossHairSize;
+
+ sfxHandle_t readySound;
+
+ qboolean disableIn3rdPerson;
+
+ weaponInfoMode_t wim[ WPM_NUM_WEAPONMODES ];
+} weaponInfo_t;
+
+typedef struct upgradeInfo_s
+{
+ qboolean registered;
+ char *humanName;
+
+ qhandle_t upgradeIcon;
+} upgradeInfo_t;
+
+typedef struct
+{
+ qboolean looped;
+ qboolean enabled;
+
+ sfxHandle_t sound;
+} sound_t;
+
+typedef struct
+{
+ qhandle_t models[ MAX_BUILDABLE_MODELS ];
+ animation_t animations[ MAX_BUILDABLE_ANIMATIONS ];
+
+ //same number of sounds as animations
+ sound_t sounds[ MAX_BUILDABLE_ANIMATIONS ];
+} buildableInfo_t;
+
+#define MAX_REWARDSTACK 10
+#define MAX_SOUNDBUFFER 20
+
+//======================================================================
+
+//TA:
+typedef struct
+{
+ vec3_t alienBuildablePos[ MAX_GENTITIES ];
+ int alienBuildableTimes[ MAX_GENTITIES ];
+ int numAlienBuildables;
+
+ vec3_t humanBuildablePos[ MAX_GENTITIES ];
+ int numHumanBuildables;
+
+ vec3_t alienClientPos[ MAX_CLIENTS ];
+ int numAlienClients;
+
+ vec3_t humanClientPos[ MAX_CLIENTS ];
+ int numHumanClients;
+
+ int lastUpdateTime;
+ vec3_t origin;
+ vec3_t vangles;
+} entityPos_t;
+
+typedef struct
+{
+ int time;
+ int length;
+} consoleLine_t;
+
+#define MAX_CONSOLE_TEXT 8192
+#define MAX_CONSOLE_LINES 32
+
+// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action
+// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after
+
+#define MAX_PREDICTED_EVENTS 16
+
+#define NUM_SAVED_STATES ( CMD_BACKUP + 2 )
+
+typedef struct
+{
+ int clientFrame; // incremented each frame
+
+ int clientNum;
+
+ qboolean demoPlayback;
+ qboolean levelShot; // taking a level menu screenshot
+ int deferredPlayerLoading;
+ qboolean loading; // don't defer players at initial startup
+ qboolean intermissionStarted; // don't play voice rewards, because game will end shortly
+
+ // there are only one or two snapshot_t that are relevent at a time
+ int latestSnapshotNum; // the number of snapshots the client system has received
+ int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet
+
+ snapshot_t *snap; // cg.snap->serverTime <= cg.time
+ snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL
+ snapshot_t activeSnapshots[ 2 ];
+
+ float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) /
+ // (cg.nextFrame->serverTime - cg.frame->serverTime)
+
+ qboolean thisFrameTeleport;
+ qboolean nextFrameTeleport;
+
+ int frametime; // cg.time - cg.oldTime
+
+ int time; // this is the time value that the client
+ // is rendering at.
+ int oldTime; // time at last frame, used for missile trails and prediction checking
+
+ int physicsTime; // either cg.snap->time or cg.nextSnap->time
+
+ int timelimitWarnings; // 5 min, 1 min, overtime
+ int fraglimitWarnings;
+
+ qboolean mapRestart; // set on a map restart to set back the weapon
+
+ qboolean renderingThirdPerson; // during deaths, chasecams, etc
+
+ // prediction state
+ qboolean hyperspace; // true if prediction has hit a trigger_teleport
+ playerState_t predictedPlayerState;
+ pmoveExt_t pmext;
+ centity_t predictedPlayerEntity;
+ qboolean validPPS; // clear until the first call to CG_PredictPlayerState
+ int predictedErrorTime;
+ vec3_t predictedError;
+
+ int eventSequence;
+ int predictableEvents[MAX_PREDICTED_EVENTS];
+
+ float stepChange; // for stair up smoothing
+ int stepTime;
+
+ float duckChange; // for duck viewheight smoothing
+ int duckTime;
+
+ float landChange; // for landing hard
+ int landTime;
+
+ // input state sent to server
+ int weaponSelect;
+
+ // auto rotating items
+ vec3_t autoAngles;
+ vec3_t autoAxis[ 3 ];
+ vec3_t autoAnglesFast;
+ vec3_t autoAxisFast[ 3 ];
+
+ // view rendering
+ refdef_t refdef;
+ vec3_t refdefViewAngles; // will be converted to refdef.viewaxis
+
+ // zoom key
+ qboolean zoomed;
+ int zoomTime;
+ float zoomSensitivity;
+
+ // information screen text during loading
+ char infoScreenText[ MAX_STRING_CHARS ];
+
+ // scoreboard
+ int scoresRequestTime;
+ int numScores;
+ int selectedScore;
+ int teamScores[ 2 ];
+ score_t scores[MAX_CLIENTS];
+ qboolean showScores;
+ qboolean scoreBoardShowing;
+ int scoreFadeTime;
+ char killerName[ MAX_NAME_LENGTH ];
+ char spectatorList[ MAX_STRING_CHARS ]; // list of names
+ int spectatorLen; // length of list
+ float spectatorWidth; // width in device units
+ int spectatorTime; // next time to offset
+ int spectatorPaintX; // current paint x
+ int spectatorPaintX2; // current paint x
+ int spectatorOffset; // current offset from start
+ int spectatorPaintLen; // current offset from start
+
+ // centerprinting
+ int centerPrintTime;
+ int centerPrintCharWidth;
+ int centerPrintY;
+ char centerPrint[ 1024 ];
+ int centerPrintLines;
+
+ // low ammo warning state
+ int lowAmmoWarning; // 1 = low, 2 = empty
+
+ // kill timers for carnage reward
+ int lastKillTime;
+
+ // crosshair client ID
+ int crosshairClientNum;
+ int crosshairClientTime;
+
+ // powerup active flashing
+ int powerupActive;
+ int powerupTime;
+
+ // attacking player
+ int attackerTime;
+ int voiceTime;
+
+ // reward medals
+ int rewardStack;
+ int rewardTime;
+ int rewardCount[ MAX_REWARDSTACK ];
+ qhandle_t rewardShader[ MAX_REWARDSTACK ];
+ qhandle_t rewardSound[ MAX_REWARDSTACK ];
+
+ // sound buffer mainly for announcer sounds
+ int soundBufferIn;
+ int soundBufferOut;
+ int soundTime;
+ qhandle_t soundBuffer[ MAX_SOUNDBUFFER ];
+
+ // for voice chat buffer
+ int voiceChatTime;
+ int voiceChatBufferIn;
+ int voiceChatBufferOut;
+
+ // warmup countdown
+ int warmup;
+ int warmupCount;
+
+ //==========================
+
+ int itemPickup;
+ int itemPickupTime;
+ int itemPickupBlendTime; // the pulse around the crosshair is timed seperately
+
+ int weaponSelectTime;
+ int weaponAnimation;
+ int weaponAnimationTime;
+
+ // blend blobs
+ float damageTime;
+ float damageX, damageY, damageValue;
+
+ // status bar head
+ float headYaw;
+ float headEndPitch;
+ float headEndYaw;
+ int headEndTime;
+ float headStartPitch;
+ float headStartYaw;
+ int headStartTime;
+
+ // view movement
+ float v_dmg_time;
+ float v_dmg_pitch;
+ float v_dmg_roll;
+
+ vec3_t kick_angles; // weapon kicks
+ vec3_t kick_origin;
+
+ // temp working variables for player view
+ float bobfracsin;
+ int bobcycle;
+ float xyspeed;
+ int nextOrbitTime;
+
+ // development tool
+ refEntity_t testModelEntity;
+ refEntity_t testModelBarrelEntity;
+ char testModelName[MAX_QPATH];
+ char testModelBarrelName[MAX_QPATH];
+ qboolean testGun;
+
+ int spawnTime; //TA: fovwarp
+ int weapon1Time; //TA: time when BUTTON_ATTACK went t->f f->t
+ int weapon2Time; //TA: time when BUTTON_ATTACK2 went t->f f->t
+ int weapon3Time; //TA: time when BUTTON_USE_HOLDABLE went t->f f->t
+ qboolean weapon1Firing;
+ qboolean weapon2Firing;
+ qboolean weapon3Firing;
+
+ int poisonedTime;
+
+ vec3_t lastNormal; //TA: view smoothage
+ vec3_t lastVangles; //TA: view smoothage
+ smooth_t sList[ MAXSMOOTHS ]; //TA: WW smoothing
+
+ int forwardMoveTime; //TA: for struggling
+ int rightMoveTime;
+ int upMoveTime;
+
+ float charModelFraction; //TA: loading percentages
+ float mediaFraction;
+ float buildablesFraction;
+
+ int lastBuildAttempt;
+ int lastEvolveAttempt;
+
+ char consoleText[ MAX_CONSOLE_TEXT ];
+ consoleLine_t consoleLines[ MAX_CONSOLE_LINES ];
+ int numConsoleLines;
+
+ particleSystem_t *poisonCloudPS;
+
+ float painBlendValue;
+ float painBlendTarget;
+ int lastHealth;
+
+ int lastPredictedCommand;
+ int lastServerTime;
+ playerState_t savedPmoveStates[ NUM_SAVED_STATES ];
+ int stateHead, stateTail;
+ int ping;
+} cg_t;
+
+
+// all of the model, shader, and sound references that are
+// loaded at gamestate time are stored in cgMedia_t
+// Other media that can be tied to clients, weapons, or items are
+// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t
+typedef struct
+{
+ qhandle_t charsetShader;
+ qhandle_t whiteShader;
+ qhandle_t outlineShader;
+
+ qhandle_t level2ZapTS;
+
+ qhandle_t balloonShader;
+ qhandle_t connectionShader;
+
+ qhandle_t viewBloodShader;
+ qhandle_t tracerShader;
+ qhandle_t crosshairShader[ WP_NUM_WEAPONS ];
+ qhandle_t backTileShader;
+
+ qhandle_t creepShader;
+
+ qhandle_t scannerShader;
+ qhandle_t scannerBlipShader;
+ qhandle_t scannerLineShader;
+
+
+ qhandle_t numberShaders[ 11 ];
+
+ qhandle_t shadowMarkShader;
+ qhandle_t wakeMarkShader;
+
+ // buildable shaders
+ qhandle_t greenBuildShader;
+ qhandle_t redBuildShader;
+ qhandle_t humanSpawningShader;
+
+ // disconnect
+ qhandle_t disconnectPS;
+ qhandle_t disconnectSound;
+
+ // sounds
+ sfxHandle_t tracerSound;
+ sfxHandle_t selectSound;
+ sfxHandle_t footsteps[ FOOTSTEP_TOTAL ][ 4 ];
+ sfxHandle_t talkSound;
+ sfxHandle_t alienTalkSound;
+ sfxHandle_t humanTalkSound;
+ sfxHandle_t landSound;
+ sfxHandle_t fallSound;
+
+ sfxHandle_t hardBounceSound1;
+ sfxHandle_t hardBounceSound2;
+
+ sfxHandle_t voteNow;
+ sfxHandle_t votePassed;
+ sfxHandle_t voteFailed;
+
+ sfxHandle_t watrInSound;
+ sfxHandle_t watrOutSound;
+ sfxHandle_t watrUnSound;
+
+ sfxHandle_t jetpackDescendSound;
+ sfxHandle_t jetpackIdleSound;
+ sfxHandle_t jetpackAscendSound;
+
+ qhandle_t jetPackDescendPS;
+ qhandle_t jetPackHoverPS;
+ qhandle_t jetPackAscendPS;
+
+ sfxHandle_t medkitUseSound;
+
+ sfxHandle_t alienStageTransition;
+ sfxHandle_t humanStageTransition;
+
+ sfxHandle_t alienOvermindAttack;
+ sfxHandle_t alienOvermindDying;
+ sfxHandle_t alienOvermindSpawns;
+
+ sfxHandle_t alienBuildableExplosion;
+ sfxHandle_t alienBuildableDamage;
+ sfxHandle_t alienBuildablePrebuild;
+ sfxHandle_t humanBuildableExplosion;
+ sfxHandle_t humanBuildablePrebuild;
+ sfxHandle_t humanBuildableDamage[ 4 ];
+
+ sfxHandle_t alienL1Grab;
+ sfxHandle_t alienL4ChargePrepare;
+ sfxHandle_t alienL4ChargeStart;
+
+ qhandle_t cursor;
+ qhandle_t selectCursor;
+ qhandle_t sizeCursor;
+
+ //light armour
+ qhandle_t larmourHeadSkin;
+ qhandle_t larmourLegsSkin;
+ qhandle_t larmourTorsoSkin;
+
+ qhandle_t jetpackModel;
+ qhandle_t jetpackFlashModel;
+ qhandle_t battpackModel;
+
+ sfxHandle_t repeaterUseSound;
+
+ sfxHandle_t buildableRepairSound;
+ sfxHandle_t buildableRepairedSound;
+
+ qhandle_t poisonCloudPS;
+ qhandle_t alienEvolvePS;
+ qhandle_t alienAcidTubePS;
+
+ sfxHandle_t alienEvolveSound;
+
+ qhandle_t humanBuildableDamagedPS;
+ qhandle_t humanBuildableDestroyedPS;
+ qhandle_t alienBuildableDamagedPS;
+ qhandle_t alienBuildableDestroyedPS;
+
+ qhandle_t alienBleedPS;
+ qhandle_t humanBleedPS;
+
+ qhandle_t teslaZapTS;
+
+ sfxHandle_t lCannonWarningSound;
+
+ qhandle_t buildWeaponTimerPie[ 8 ];
+ qhandle_t upgradeClassIconShader;
+} cgMedia_t;
+
+typedef struct
+{
+ qhandle_t frameShader;
+ qhandle_t overlayShader;
+ qhandle_t noPowerShader;
+ qhandle_t markedShader;
+ vec4_t healthSevereColor;
+ vec4_t healthHighColor;
+ vec4_t healthElevatedColor;
+ vec4_t healthGuardedColor;
+ vec4_t healthLowColor;
+ int frameHeight;
+ int frameWidth;
+ int healthPadding;
+ int overlayHeight;
+ int overlayWidth;
+ float verticalMargin;
+ float horizontalMargin;
+ vec4_t foreColor;
+ vec4_t backColor;
+ qboolean loaded;
+} buildStat_t;
+
+
+// The client game static (cgs) structure hold everything
+// loaded or calculated from the gamestate. It will NOT
+// be cleared when a tournement restart is done, allowing
+// all clients to begin playing instantly
+typedef struct
+{
+ gameState_t gameState; // gamestate from server
+ glconfig_t glconfig; // rendering configuration
+ float screenXScale; // derived from glconfig
+ float screenYScale;
+ float screenXBias;
+
+ int serverCommandSequence; // reliable command stream counter
+ int processedSnapshotNum; // the number of snapshots cgame has requested
+
+ qboolean localServer; // detected on startup by checking sv_running
+
+ // parsed from serverinfo
+ int timelimit;
+ int maxclients;
+ char mapname[ MAX_QPATH ];
+ qboolean markDeconstruct; // Whether or not buildables are marked
+
+ int voteTime;
+ int voteYes;
+ int voteNo;
+ qboolean voteModified; // beep whenever changed
+ char voteString[ MAX_STRING_TOKENS ];
+
+ int teamVoteTime[ 2 ];
+ int teamVoteYes[ 2 ];
+ int teamVoteNo[ 2 ];
+ qboolean teamVoteModified[ 2 ]; // beep whenever changed
+ char teamVoteString[ 2 ][ MAX_STRING_TOKENS ];
+
+ int levelStartTime;
+
+ int scores1, scores2; // from configstrings
+
+ qboolean newHud;
+
+ int alienBuildPoints;
+ int alienBuildPointsTotal;
+ int humanBuildPoints;
+ int humanBuildPointsTotal;
+ int humanBuildPointsPowered;
+
+ int alienStage;
+ int humanStage;
+ int alienKills;
+ int humanKills;
+ int alienNextStageThreshold;
+ int humanNextStageThreshold;
+
+ int numAlienSpawns;
+ int numHumanSpawns;
+
+ //
+ // locally derived information from gamestate
+ //
+ qhandle_t gameModels[ MAX_MODELS ];
+ qhandle_t gameShaders[ MAX_GAME_SHADERS ];
+ qhandle_t gameParticleSystems[ MAX_GAME_PARTICLE_SYSTEMS ];
+ sfxHandle_t gameSounds[ MAX_SOUNDS ];
+
+ int numInlineModels;
+ qhandle_t inlineDrawModel[ MAX_MODELS ];
+ vec3_t inlineModelMidpoints[ MAX_MODELS ];
+
+ clientInfo_t clientinfo[ MAX_CLIENTS ];
+
+ //TA: corpse info
+ clientInfo_t corpseinfo[ MAX_CLIENTS ];
+
+ int cursorX;
+ int cursorY;
+ qboolean eventHandling;
+ qboolean mouseCaptured;
+ qboolean sizingHud;
+ void *capturedItem;
+ qhandle_t activeCursor;
+
+ buildStat_t alienBuildStat;
+ buildStat_t humanBuildStat;
+
+ // media
+ cgMedia_t media;
+} cgs_t;
+
+//==============================================================================
+
+extern cgs_t cgs;
+extern cg_t cg;
+extern centity_t cg_entities[ MAX_GENTITIES ];
+
+//TA: weapon limit expanded:
+//extern weaponInfo_t cg_weapons[MAX_WEAPONS];
+extern weaponInfo_t cg_weapons[ 32 ];
+//TA: upgrade infos:
+extern upgradeInfo_t cg_upgrades[ 32 ];
+
+//TA: buildable infos:
+extern buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ];
+
+extern markPoly_t cg_markPolys[ MAX_MARK_POLYS ];
+
+extern vmCvar_t cg_centertime;
+extern vmCvar_t cg_runpitch;
+extern vmCvar_t cg_runroll;
+extern vmCvar_t cg_bobup;
+extern vmCvar_t cg_bobpitch;
+extern vmCvar_t cg_bobroll;
+extern vmCvar_t cg_swingSpeed;
+extern vmCvar_t cg_shadows;
+extern vmCvar_t cg_gibs;
+extern vmCvar_t cg_drawTimer;
+extern vmCvar_t cg_drawClock;
+extern vmCvar_t cg_drawFPS;
+extern vmCvar_t cg_drawDemoState;
+extern vmCvar_t cg_drawSnapshot;
+extern vmCvar_t cg_draw3dIcons;
+extern vmCvar_t cg_drawIcons;
+extern vmCvar_t cg_drawAmmoWarning;
+extern vmCvar_t cg_drawCrosshair;
+extern vmCvar_t cg_drawCrosshairNames;
+extern vmCvar_t cg_drawRewards;
+extern vmCvar_t cg_drawTeamOverlay;
+extern vmCvar_t cg_teamOverlayUserinfo;
+extern vmCvar_t cg_crosshairX;
+extern vmCvar_t cg_crosshairY;
+extern vmCvar_t cg_drawStatus;
+extern vmCvar_t cg_draw2D;
+extern vmCvar_t cg_animSpeed;
+extern vmCvar_t cg_debugAnim;
+extern vmCvar_t cg_debugPosition;
+extern vmCvar_t cg_debugEvents;
+extern vmCvar_t cg_teslaTrailTime;
+extern vmCvar_t cg_railTrailTime;
+extern vmCvar_t cg_errorDecay;
+extern vmCvar_t cg_nopredict;
+extern vmCvar_t cg_debugMove;
+extern vmCvar_t cg_noPlayerAnims;
+extern vmCvar_t cg_showmiss;
+extern vmCvar_t cg_footsteps;
+extern vmCvar_t cg_addMarks;
+extern vmCvar_t cg_brassTime;
+extern vmCvar_t cg_gun_frame;
+extern vmCvar_t cg_gun_x;
+extern vmCvar_t cg_gun_y;
+extern vmCvar_t cg_gun_z;
+extern vmCvar_t cg_drawGun;
+extern vmCvar_t cg_viewsize;
+extern vmCvar_t cg_tracerChance;
+extern vmCvar_t cg_tracerWidth;
+extern vmCvar_t cg_tracerLength;
+extern vmCvar_t cg_autoswitch;
+extern vmCvar_t cg_ignore;
+extern vmCvar_t cg_simpleItems;
+extern vmCvar_t cg_fov;
+extern vmCvar_t cg_zoomFov;
+extern vmCvar_t cg_thirdPersonRange;
+extern vmCvar_t cg_thirdPersonAngle;
+extern vmCvar_t cg_thirdPerson;
+extern vmCvar_t cg_stereoSeparation;
+extern vmCvar_t cg_lagometer;
+extern vmCvar_t cg_drawAttacker;
+extern vmCvar_t cg_synchronousClients;
+extern vmCvar_t cg_stats;
+extern vmCvar_t cg_forceModel;
+extern vmCvar_t cg_buildScript;
+extern vmCvar_t cg_paused;
+extern vmCvar_t cg_blood;
+extern vmCvar_t cg_predictItems;
+extern vmCvar_t cg_deferPlayers;
+extern vmCvar_t cg_drawFriend;
+extern vmCvar_t cg_teamChatsOnly;
+extern vmCvar_t cg_noVoiceChats;
+extern vmCvar_t cg_noVoiceText;
+extern vmCvar_t cg_scorePlum;
+extern vmCvar_t cg_smoothClients;
+extern vmCvar_t pmove_fixed;
+extern vmCvar_t pmove_msec;
+//extern vmCvar_t cg_pmove_fixed;
+extern vmCvar_t cg_cameraOrbit;
+extern vmCvar_t cg_cameraOrbitDelay;
+extern vmCvar_t cg_timescaleFadeEnd;
+extern vmCvar_t cg_timescaleFadeSpeed;
+extern vmCvar_t cg_timescale;
+extern vmCvar_t cg_cameraMode;
+extern vmCvar_t cg_smallFont;
+extern vmCvar_t cg_bigFont;
+extern vmCvar_t cg_noTaunt;
+extern vmCvar_t cg_noProjectileTrail;
+extern vmCvar_t cg_oldRail;
+extern vmCvar_t cg_oldRocket;
+extern vmCvar_t cg_oldPlasma;
+extern vmCvar_t cg_trueLightning;
+extern vmCvar_t cg_creepRes;
+extern vmCvar_t cg_drawSurfNormal;
+extern vmCvar_t cg_drawBBOX;
+extern vmCvar_t cg_debugAlloc;
+extern vmCvar_t cg_wwSmoothTime;
+extern vmCvar_t cg_wwFollow;
+extern vmCvar_t cg_wwToggle;
+extern vmCvar_t cg_depthSortParticles;
+extern vmCvar_t cg_bounceParticles;
+extern vmCvar_t cg_consoleLatency;
+extern vmCvar_t cg_lightFlare;
+extern vmCvar_t cg_debugParticles;
+extern vmCvar_t cg_debugTrails;
+extern vmCvar_t cg_debugPVS;
+extern vmCvar_t cg_disableWarningDialogs;
+extern vmCvar_t cg_disableScannerPlane;
+extern vmCvar_t cg_tutorial;
+
+extern vmCvar_t cg_painBlendUpRate;
+extern vmCvar_t cg_painBlendDownRate;
+extern vmCvar_t cg_painBlendMax;
+extern vmCvar_t cg_painBlendScale;
+extern vmCvar_t cg_painBlendZoom;
+
+//TA: hack to get class an carriage through to UI module
+extern vmCvar_t ui_currentClass;
+extern vmCvar_t ui_carriage;
+extern vmCvar_t ui_stages;
+extern vmCvar_t ui_dialog;
+extern vmCvar_t ui_loading;
+extern vmCvar_t ui_voteActive;
+extern vmCvar_t ui_alienTeamVoteActive;
+extern vmCvar_t ui_humanTeamVoteActive;
+
+extern vmCvar_t cg_debugRandom;
+
+extern vmCvar_t cg_optimizePrediction;
+extern vmCvar_t cg_projectileNudge;
+extern vmCvar_t cg_unlagged;
+
+//
+// cg_main.c
+//
+const char *CG_ConfigString( int index );
+const char *CG_Argv( int arg );
+
+void QDECL CG_Printf( const char *msg, ... );
+void QDECL CG_Error( const char *msg, ... );
+
+void CG_StartMusic( void );
+int CG_PlayerCount( void );
+
+void CG_UpdateCvars( void );
+
+int CG_CrosshairPlayer( void );
+int CG_LastAttacker( void );
+void CG_LoadMenus( const char *menuFile );
+void CG_KeyEvent( int key, qboolean down );
+void CG_MouseEvent( int x, int y );
+void CG_EventHandling( int type );
+void CG_SetScoreSelection( void *menu );
+void CG_BuildSpectatorString( void );
+
+qboolean CG_FileExists( char *filename );
+void CG_RemoveNotifyLine( void );
+void CG_AddNotifyText( void );
+
+
+//
+// cg_view.c
+//
+void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); //TA
+void CG_TestModel_f( void );
+void CG_TestGun_f( void );
+void CG_TestModelNextFrame_f( void );
+void CG_TestModelPrevFrame_f( void );
+void CG_TestModelNextSkin_f( void );
+void CG_TestModelPrevSkin_f( void );
+void CG_AddBufferedSound( sfxHandle_t sfx );
+void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback );
+
+
+//
+// cg_drawtools.c
+//
+void CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader );
+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_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor,
+ vec4_t tcolor, float amount, qhandle_t hShader );
+
+int CG_DrawStrlen( const char *str );
+
+float *CG_FadeColor( int startMsec, int totalMsec );
+void CG_TileClear( void );
+void CG_ColorForHealth( vec4_t hcolor );
+void CG_GetColorForHealth( int health, int armor, vec4_t hcolor );
+
+void CG_DrawRect( float x, float y, float width, float height, float size, const float *color );
+void CG_DrawSides(float x, float y, float w, float h, float size);
+void CG_DrawTopBottom(float x, float y, float w, float h, float size);
+qboolean CG_WorldToScreen( vec3_t point, float *x, float *y );
+char *CG_KeyBinding( const char *bind );
+
+
+//
+// cg_draw.c
+//
+extern int sortedTeamPlayers[ TEAM_MAXOVERLAY ];
+extern int numSortedTeamPlayers;
+
+void CG_AddLagometerFrameInfo( void );
+void CG_AddLagometerSnapshotInfo( snapshot_t *snap );
+void CG_CenterPrint( const char *str, int y, int charWidth );
+void CG_DrawActive( stereoFrame_t stereoView );
+void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y,
+ int ownerDraw, int ownerDrawFlags, int align, float special,
+ float scale, vec4_t color, qhandle_t shader, int textStyle);
+void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style );
+int CG_Text_Width( const char *text, float scale, int limit );
+int CG_Text_Height( const char *text, float scale, int limit );
+float CG_GetValue(int ownerDraw);
+void CG_RunMenuScript(char **args);
+void CG_SetPrintString( int type, const char *p );
+void CG_GetTeamColor( vec4_t *color );
+const char *CG_GetKillerText( void );
+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_DrawLoadingScreen( void );
+void CG_UpdateMediaFraction( float newFract );
+void CG_ResetPainBlend( void );
+void CG_DrawField( float x, float y, int width, float cw, float ch, int value );
+
+//
+// cg_players.c
+//
+void CG_Player( centity_t *cent );
+void CG_Corpse( centity_t *cent );
+void CG_ResetPlayerEntity( centity_t *cent );
+void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int team );
+void CG_NewClientInfo( int clientNum );
+void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin );
+sfxHandle_t CG_CustomSound( int clientNum, const char *soundName );
+void CG_PlayerDisconnect( vec3_t org );
+void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum );
+qboolean CG_AtHighestClass( void );
+
+//
+// cg_buildable.c
+//
+void CG_GhostBuildable( buildable_t buildable );
+void CG_Buildable( centity_t *cent );
+void CG_BuildableStatusParse( const char *filename, buildStat_t *bs );
+void CG_DrawBuildableStatus( void );
+void CG_InitBuildables( void );
+void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir );
+void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir );
+
+//
+// cg_animation.c
+//
+void CG_RunLerpFrame( lerpFrame_t *lf );
+
+//
+// cg_animmapobj.c
+//
+void CG_AnimMapObj( centity_t *cent );
+void CG_ModelDoor( centity_t *cent );
+
+//
+// cg_predict.c
+//
+
+#define MAGIC_TRACE_HACK -2
+
+void CG_BuildSolidList( void );
+int CG_PointContents( const vec3_t point, int passEntityNum );
+void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs,
+ const vec3_t end, int skipNumber, int mask );
+void CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs,
+ const vec3_t end, int skipNumber, int mask );
+void CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end,
+ const float startRadius, const float endRadius, int skipNumber, int mask );
+void CG_PredictPlayerState( void );
+
+
+//
+// cg_events.c
+//
+void CG_CheckEvents( centity_t *cent );
+void CG_EntityEvent( centity_t *cent, vec3_t position );
+void CG_PainEvent( centity_t *cent, int health );
+
+
+//
+// cg_ents.c
+//
+void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs );
+void CG_SetEntitySoundPosition( centity_t *cent );
+void CG_AddPacketEntities( void );
+void CG_Beam( centity_t *cent );
+void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out );
+
+void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName );
+void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName );
+
+
+
+
+//
+// cg_weapons.c
+//
+void CG_NextWeapon_f( void );
+void CG_PrevWeapon_f( void );
+void CG_Weapon_f( void );
+
+void CG_InitUpgrades( void );
+void CG_RegisterUpgrade( int upgradeNum );
+void CG_InitWeapons( void );
+void CG_RegisterWeapon( int weaponNum );
+
+void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode );
+void CG_MissileHitWall( weapon_t weapon, weaponMode_t weaponMode, int clientNum,
+ vec3_t origin, vec3_t dir, impactSound_t soundType );
+void CG_MissileHitPlayer( weapon_t weapon, weaponMode_t weaponMode, vec3_t origin, vec3_t dir, int entityNum );
+void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum );
+void CG_ShotgunFire( entityState_t *es );
+
+void CG_AddViewWeapon (playerState_t *ps);
+void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent );
+void CG_DrawItemSelect( rectDef_t *rect, vec4_t color );
+void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle );
+
+
+//
+// cg_scanner.c
+//
+void CG_UpdateEntityPositions( void );
+void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color );
+void CG_AlienSense( rectDef_t *rect );
+
+//
+// cg_marks.c
+//
+void CG_InitMarkPolys( void );
+void CG_AddMarks( void );
+void CG_ImpactMark( qhandle_t markShader,
+ const vec3_t origin, const vec3_t dir,
+ float orientation,
+ float r, float g, float b, float a,
+ qboolean alphaFade,
+ float radius, qboolean temporary );
+
+//
+// cg_snapshot.c
+//
+void CG_ProcessSnapshots( void );
+
+//
+// cg_consolecmds.c
+//
+qboolean CG_ConsoleCommand( void );
+void CG_InitConsoleCommands( void );
+qboolean CG_RequestScores( void );
+
+//
+// cg_servercmds.c
+//
+void CG_ExecuteNewServerCommands( int latestSequence );
+void CG_ParseServerinfo( void );
+void CG_SetConfigValues( void );
+void CG_ShaderStateChanged(void);
+
+//
+// cg_playerstate.c
+//
+void CG_Respawn( void );
+void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops );
+void CG_CheckChangedPredictableEvents( playerState_t *ps );
+
+//
+// cg_mem.c
+//
+void CG_InitMemory( void );
+void *CG_Alloc( int size );
+void CG_Free( void *ptr );
+void CG_DefragmentMemory( void );
+
+//
+// cg_attachment.c
+//
+qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v );
+qboolean CG_AttachmentDir( attachment_t *a, vec3_t v );
+qboolean CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] );
+qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v );
+int CG_AttachmentCentNum( attachment_t *a );
+
+qboolean CG_Attached( attachment_t *a );
+
+void CG_AttachToPoint( attachment_t *a );
+void CG_AttachToCent( attachment_t *a );
+void CG_AttachToTag( attachment_t *a );
+void CG_AttachToParticle( attachment_t *a );
+void CG_SetAttachmentPoint( attachment_t *a, vec3_t v );
+void CG_SetAttachmentCent( attachment_t *a, centity_t *cent );
+void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent,
+ qhandle_t model, char *tagName );
+void CG_SetAttachmentParticle( attachment_t *a, particle_t *p );
+
+void CG_SetAttachmentOffset( attachment_t *a, vec3_t v );
+
+//
+// cg_particles.c
+//
+void CG_LoadParticleSystems( void );
+qhandle_t CG_RegisterParticleSystem( char *name );
+
+particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle );
+void CG_DestroyParticleSystem( particleSystem_t **ps );
+
+qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps );
+qboolean CG_IsParticleSystemValid( particleSystem_t **ps );
+
+void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal );
+
+void CG_AddParticles( void );
+
+void CG_ParticleSystemEntity( centity_t *cent );
+
+void CG_TestPS_f( void );
+void CG_DestroyTestPS_f( void );
+
+//
+// cg_trails.c
+//
+void CG_LoadTrailSystems( void );
+qhandle_t CG_RegisterTrailSystem( char *name );
+
+trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle );
+void CG_DestroyTrailSystem( trailSystem_t **ts );
+
+qboolean CG_IsTrailSystemValid( trailSystem_t **ts );
+
+void CG_AddTrails( void );
+
+void CG_TestTS_f( void );
+void CG_DestroyTestTS_f( void );
+
+//
+// cg_ptr.c
+//
+int CG_ReadPTRCode( void );
+void CG_WritePTRCode( int code );
+
+//
+// cg_tutorial.c
+//
+const char *CG_TutorialText( void );
+
+//
+//===============================================
+
+//
+// system traps
+// These functions are how the cgame communicates with the main game system
+//
+
+
+// print message on the local console
+void trap_Print( const char *fmt );
+
+// abort the game
+void trap_Error( const char *fmt );
+
+// milliseconds should only be used for performance tuning, never
+// for anything game related. Get time from the CG_DrawActiveFrame parameter
+int trap_Milliseconds( void );
+
+// console variable interaction
+void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
+void trap_Cvar_Update( vmCvar_t *vmCvar );
+void trap_Cvar_Set( const char *var_name, const char *value );
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+
+// ServerCommand and ConsoleCommand parameter access
+int trap_Argc( void );
+void trap_Argv( int n, char *buffer, int bufferLength );
+void trap_Args( char *buffer, int bufferLength );
+void trap_LiteralArgs( char *buffer, int bufferLength );
+
+// filesystem access
+// returns length of file
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
+void trap_FS_Read( void *buffer, int len, fileHandle_t f );
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f );
+void trap_FS_FCloseFile( fileHandle_t f );
+void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t
+int trap_FS_GetFileList( const char *path, const char *extension,
+ char *listbuf, int bufsize );
+
+// add commands to the local console as if they were typed in
+// for map changing, etc. The command is not executed immediately,
+// but will be executed in order the next time console commands
+// are processed
+void trap_SendConsoleCommand( const char *text );
+
+// register a command name so the console can perform command completion.
+// FIXME: replace this with a normal console command "defineCommand"?
+void trap_AddCommand( const char *cmdName );
+
+// send a string to the server over the network
+void trap_SendClientCommand( const char *s );
+
+// force a screen update, only used during gamestate load
+void trap_UpdateScreen( void );
+
+// model collision
+void trap_CM_LoadMap( const char *mapname );
+int trap_CM_NumInlineModels( void );
+clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels
+clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs );
+int trap_CM_PointContents( const vec3_t p, clipHandle_t model );
+int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles );
+void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask );
+void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles );
+void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask );
+void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles );
+void trap_CM_BiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask );
+void trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask,
+ const vec3_t origin );
+
+// Returns the projection of a polygon onto the solid brushes in the world
+int trap_CM_MarkFragments( int numPoints, const vec3_t *points,
+ const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer,
+ int maxFragments, markFragment_t *fragmentBuffer );
+
+// normal sounds will have their volume dynamically changed as their entity
+// moves and the listener moves
+void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx );
+void trap_S_StopLoopingSound( int entnum );
+
+// a local sound is always played full volume
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum );
+void trap_S_ClearLoopingSounds( qboolean killall );
+void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin );
+
+// respatialize recalculates the volumes of sound as they should be heard by the
+// given entityNum and position
+void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater );
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music
+void trap_S_StopBackgroundTrack( void );
+
+
+void trap_R_LoadWorldMap( const char *mapname );
+
+// all media should be registered during level startup to prevent
+// hitches during gameplay
+qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found
+qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found
+qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found
+qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found
+
+// a scene is built up by calls to R_ClearScene and the various R_Add functions.
+// Nothing is drawn until R_RenderScene is called.
+void trap_R_ClearScene( void );
+void trap_R_AddRefEntityToScene( const refEntity_t *re );
+
+// polys are intended for simple wall marks, not really for doing
+// significant construction
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts );
+void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys );
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir );
+void trap_R_RenderScene( const refdef_t *fd );
+void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1
+void trap_R_DrawStretchPic( float x, float y, float w, float h,
+ float s1, float t1, float s2, float t2, qhandle_t hShader );
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs );
+int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame,
+ float frac, const char *tagName );
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset );
+
+// The glconfig_t will not change during the life of a cgame.
+// If it needs to change, the entire cgame will be restarted, because
+// all the qhandle_t are then invalid.
+void trap_GetGlconfig( glconfig_t *glconfig );
+
+// the gamestate should be grabbed at startup, and whenever a
+// configstring changes
+void trap_GetGameState( gameState_t *gamestate );
+
+// cgame will poll each frame to see if a newer snapshot has arrived
+// that it is interested in. The time is returned seperately so that
+// snapshot latency can be calculated.
+void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime );
+
+#ifdef MODULE_INTERFACE_11
+typedef struct {
+ int commandTime; // cmd->serverTime of last executed command
+ int pm_type;
+ int bobCycle; // for view bobbing and footstep generation
+ int pm_flags; // ducked, jump_held, etc
+ int pm_time;
+
+ vec3_t origin;
+ vec3_t velocity;
+ int weaponTime;
+ int gravity;
+ int speed;
+ int delta_angles[3]; // add to command angles to get view direction
+ // changed by spawns, rotating objects, and teleporters
+
+ int groundEntityNum;// ENTITYNUM_NONE = in air
+
+ int legsTimer; // don't change low priority animations until this runs out
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+
+ int torsoTimer; // don't change low priority animations until this runs out
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+
+ int movementDir; // a number 0 to 7 that represents the reletive angle
+ // of movement to the view angle (axial and diagonals)
+ // when at rest, the value will remain unchanged
+ // used to twist the legs during strafing
+
+ vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL
+
+ int eFlags; // copied to entityState_t->eFlags
+
+ int eventSequence; // pmove generated events
+ int events[MAX_PS_EVENTS];
+ int eventParms[MAX_PS_EVENTS];
+
+ int externalEvent; // events set on player from another source
+ int externalEventParm;
+ int externalEventTime;
+
+ int clientNum; // ranges from 0 to MAX_CLIENTS-1
+ int weapon; // copied to entityState_t->weapon
+ int weaponstate;
+
+ vec3_t viewangles; // for fixed views
+ int viewheight;
+
+ // damage feedback
+ int damageEvent; // when it changes, latch the other parms
+ int damageYaw;
+ int damagePitch;
+ int damageCount;
+
+ int stats[MAX_STATS];
+ int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death
+ int misc[MAX_MISC]; // misc data
+ int ammo[16];
+
+ int generic1;
+ int loopSound;
+ int otherEntityNum;
+
+ // not communicated over the net at all
+ int ping; // server to game info for scoreboard
+ int pmove_framecount; // FIXME: don't transmit over the network
+ int jumppad_frame;
+ int entityEventSequence;
+} moduleAlternatePlayerState_t;
+
+typedef struct {
+ int number; // entity index
+ int eType; // entityType_t
+ int eFlags;
+
+ trajectory_t pos; // for calculating position
+ trajectory_t apos; // for calculating angles
+
+ int time;
+ int time2;
+
+ vec3_t origin;
+ vec3_t origin2;
+
+ vec3_t angles;
+ vec3_t angles2;
+
+ int otherEntityNum; // shotgun sources, etc
+ int otherEntityNum2;
+
+ int groundEntityNum; // ENTITYNUM_NONE = in air
+
+ int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24)
+ int loopSound; // constantly loop this sound
+
+ int modelindex;
+ int modelindex2;
+ int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses
+ int frame;
+
+ int solid; // for client side prediction, trap_linkentity sets this properly
+
+ int event; // impulse events -- muzzle flashes, footsteps, etc
+ int eventParm;
+
+ // for players
+ int misc; // bit flags
+ int weapon; // determines weapon and flash model, etc
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+
+ int generic1;
+} moduleAlternateEntityState_t;
+
+typedef struct
+{
+ int snapFlags; // SNAPFLAG_RATE_DELAYED, etc
+ int ping;
+
+ int serverTime; // server time the message is valid for (in msec)
+
+ byte areamask[ MAX_MAP_AREA_BYTES ]; // portalarea visibility bits
+
+ moduleAlternatePlayerState_t ps; // complete information about the current player at this time
+
+ int numEntities; // all of the entities that need to be presented
+ moduleAlternateEntityState_t entities[ MAX_ENTITIES_IN_SNAPSHOT ]; // at the time of this snapshot
+
+ int numServerCommands; // text based server commands to execute when this
+ int serverCommandSequence; // snapshot becomes current
+} moduleAlternateSnapshot_t;
+
+qboolean trap_GetSnapshot( int snapshotNumber, moduleAlternateSnapshot_t *snapshot );
+#else
+// a snapshot get can fail if the snapshot (or the entties it holds) is so
+// old that it has fallen out of the client system queue
+qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot );
+#endif
+
+// retrieve a text command from the server stream
+// the current snapshot will hold the number of the most recent command
+// qfalse can be returned if the client system handled the command
+// argc() / argv() can be used to examine the parameters of the command
+qboolean trap_GetServerCommand( int serverCommandNumber );
+
+// returns the most recent command number that can be passed to GetUserCmd
+// this will always be at least one higher than the number in the current
+// snapshot, and it may be quite a few higher if it is a fast computer on
+// a lagged connection
+int trap_GetCurrentCmdNumber( void );
+
+qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd );
+
+// used for the weapon select and zoom
+void trap_SetUserCmdValue( int stateValue, float sensitivityScale );
+
+// aids for VM testing
+void testPrintInt( char *string, int i );
+void testPrintFloat( char *string, float f );
+
+int trap_MemoryRemaining( void );
+void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font);
+qboolean trap_Key_IsDown( int keynum );
+int trap_Key_GetCatcher( void );
+void trap_Key_SetCatcher( int catcher );
+int trap_Key_GetKey( const char *binding );
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen );
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen );
+void trap_Key_SetBinding( int keynum, const char *binding );
+
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits );
+e_status trap_CIN_StopCinematic( int handle );
+e_status trap_CIN_RunCinematic( int handle );
+void trap_CIN_DrawCinematic( int handle );
+void trap_CIN_SetExtents( int handle, int x, int y, int w, int h );
+
+void trap_SnapVector( float *v );
+int trap_RealTime( qtime_t *tm );
+
+qboolean trap_loadCamera( const char *name );
+void trap_startCamera( int time );
+qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles );
+
+qboolean trap_GetEntityToken( char *buffer, int bufferSize );
+
+int trap_GetDemoState( void );
+int trap_GetDemoPos( void );
+void trap_GetDemoName( char *buffer, int size );
+
+// cg_drawCrosshair settings
+#define CROSSHAIR_ALWAYSOFF 0
+#define CROSSHAIR_RANGEDONLY 1
+#define CROSSHAIR_ALWAYSON 2
+
diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c
new file mode 100644
index 0000000..eb19dcb
--- /dev/null
+++ b/src/cgame/cg_main.c
@@ -0,0 +1,2121 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+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"
+
+#include "../ui/ui_shared.h"
+// display context for new ui stuff
+displayContextDef_t cgDC;
+
+int forceModelModificationCount = -1;
+
+void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum );
+void CG_Shutdown( void );
+char *CG_VoIPString( void );
+
+/*
+================
+vmMain
+
+This is the only way control passes into the module.
+This must be the very first function compiled into the .q3vm file
+================
+*/
+Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3,
+ int arg4, int arg5, int arg6, int arg7,
+ int arg8, int arg9, int arg10, int arg11 )
+{
+ switch( command )
+ {
+ case CG_INIT:
+ CG_Init( arg0, arg1, arg2 );
+ return 0;
+
+ case CG_SHUTDOWN:
+ CG_Shutdown( );
+ return 0;
+
+ case CG_CONSOLE_COMMAND:
+ return CG_ConsoleCommand( );
+
+ case CG_CONSOLE_TEXT:
+ CG_AddNotifyText( );
+ return 0;
+
+ case CG_DRAW_ACTIVE_FRAME:
+ CG_DrawActiveFrame( arg0, arg1, arg2 );
+ return 0;
+
+ case CG_CROSSHAIR_PLAYER:
+ return CG_CrosshairPlayer( );
+
+ case CG_LAST_ATTACKER:
+ return CG_LastAttacker( );
+
+ case CG_KEY_EVENT:
+ CG_KeyEvent( arg0, arg1 );
+ return 0;
+
+ case CG_MOUSE_EVENT:
+ cgDC.cursorx = cgs.cursorX;
+ cgDC.cursory = cgs.cursorY;
+ CG_MouseEvent( arg0, arg1 );
+ return 0;
+
+ case CG_EVENT_HANDLING:
+ CG_EventHandling( arg0 );
+ return 0;
+
+#ifndef MODULE_INTERFACE_11
+ case CG_VOIP_STRING:
+ return (intptr_t)CG_VoIPString( );
+#endif
+
+ default:
+ CG_Error( "vmMain: unknown command %i", command );
+ break;
+ }
+
+ return -1;
+}
+
+
+cg_t cg;
+cgs_t cgs;
+centity_t cg_entities[ MAX_GENTITIES ];
+
+//TA: weapons limit expanded:
+//weaponInfo_t cg_weapons[MAX_WEAPONS];
+weaponInfo_t cg_weapons[ 32 ];
+upgradeInfo_t cg_upgrades[ 32 ];
+
+buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ];
+
+vmCvar_t cg_teslaTrailTime;
+vmCvar_t cg_railTrailTime;
+vmCvar_t cg_centertime;
+vmCvar_t cg_runpitch;
+vmCvar_t cg_runroll;
+vmCvar_t cg_bobup;
+vmCvar_t cg_bobpitch;
+vmCvar_t cg_bobroll;
+vmCvar_t cg_swingSpeed;
+vmCvar_t cg_shadows;
+vmCvar_t cg_gibs;
+vmCvar_t cg_drawTimer;
+vmCvar_t cg_drawClock;
+vmCvar_t cg_drawFPS;
+vmCvar_t cg_drawDemoState;
+vmCvar_t cg_drawSnapshot;
+vmCvar_t cg_draw3dIcons;
+vmCvar_t cg_drawIcons;
+vmCvar_t cg_drawAmmoWarning;
+vmCvar_t cg_drawCrosshair;
+vmCvar_t cg_drawCrosshairNames;
+vmCvar_t cg_drawRewards;
+vmCvar_t cg_crosshairX;
+vmCvar_t cg_crosshairY;
+vmCvar_t cg_draw2D;
+vmCvar_t cg_drawStatus;
+vmCvar_t cg_animSpeed;
+vmCvar_t cg_debugAnim;
+vmCvar_t cg_debugPosition;
+vmCvar_t cg_debugEvents;
+vmCvar_t cg_errorDecay;
+vmCvar_t cg_nopredict;
+vmCvar_t cg_debugMove;
+vmCvar_t cg_noPlayerAnims;
+vmCvar_t cg_showmiss;
+vmCvar_t cg_footsteps;
+vmCvar_t cg_addMarks;
+vmCvar_t cg_brassTime;
+vmCvar_t cg_viewsize;
+vmCvar_t cg_drawGun;
+vmCvar_t cg_gun_frame;
+vmCvar_t cg_gun_x;
+vmCvar_t cg_gun_y;
+vmCvar_t cg_gun_z;
+vmCvar_t cg_tracerChance;
+vmCvar_t cg_tracerWidth;
+vmCvar_t cg_tracerLength;
+vmCvar_t cg_autoswitch;
+vmCvar_t cg_ignore;
+vmCvar_t cg_simpleItems;
+vmCvar_t cg_fov;
+vmCvar_t cg_zoomFov;
+vmCvar_t cg_thirdPerson;
+vmCvar_t cg_thirdPersonRange;
+vmCvar_t cg_thirdPersonAngle;
+vmCvar_t cg_stereoSeparation;
+vmCvar_t cg_lagometer;
+vmCvar_t cg_drawAttacker;
+vmCvar_t cg_synchronousClients;
+vmCvar_t cg_stats;
+vmCvar_t cg_buildScript;
+vmCvar_t cg_forceModel;
+vmCvar_t cg_paused;
+vmCvar_t cg_blood;
+vmCvar_t cg_predictItems;
+vmCvar_t cg_deferPlayers;
+vmCvar_t cg_drawTeamOverlay;
+vmCvar_t cg_teamOverlayUserinfo;
+vmCvar_t cg_drawFriend;
+vmCvar_t cg_teamChatsOnly;
+vmCvar_t cg_noVoiceChats;
+vmCvar_t cg_noVoiceText;
+vmCvar_t cg_hudFiles;
+vmCvar_t cg_scorePlum;
+vmCvar_t cg_smoothClients;
+vmCvar_t pmove_fixed;
+//vmCvar_t cg_pmove_fixed;
+vmCvar_t pmove_msec;
+vmCvar_t cg_pmove_msec;
+vmCvar_t cg_cameraMode;
+vmCvar_t cg_cameraOrbit;
+vmCvar_t cg_cameraOrbitDelay;
+vmCvar_t cg_timescaleFadeEnd;
+vmCvar_t cg_timescaleFadeSpeed;
+vmCvar_t cg_timescale;
+vmCvar_t cg_smallFont;
+vmCvar_t cg_bigFont;
+vmCvar_t cg_noTaunt;
+vmCvar_t cg_noProjectileTrail;
+vmCvar_t cg_oldRail;
+vmCvar_t cg_oldRocket;
+vmCvar_t cg_oldPlasma;
+vmCvar_t cg_trueLightning;
+vmCvar_t cg_creepRes;
+vmCvar_t cg_drawSurfNormal;
+vmCvar_t cg_drawBBOX;
+vmCvar_t cg_debugAlloc;
+vmCvar_t cg_wwSmoothTime;
+vmCvar_t cg_wwFollow;
+vmCvar_t cg_wwToggle;
+vmCvar_t cg_depthSortParticles;
+vmCvar_t cg_bounceParticles;
+vmCvar_t cg_consoleLatency;
+vmCvar_t cg_lightFlare;
+vmCvar_t cg_debugParticles;
+vmCvar_t cg_debugTrails;
+vmCvar_t cg_debugPVS;
+vmCvar_t cg_disableWarningDialogs;
+vmCvar_t cg_disableScannerPlane;
+vmCvar_t cg_tutorial;
+
+vmCvar_t cg_painBlendUpRate;
+vmCvar_t cg_painBlendDownRate;
+vmCvar_t cg_painBlendMax;
+vmCvar_t cg_painBlendScale;
+vmCvar_t cg_painBlendZoom;
+
+//TA: hack to get class and carriage through to UI module
+vmCvar_t ui_currentClass;
+vmCvar_t ui_carriage;
+vmCvar_t ui_stages;
+vmCvar_t ui_dialog;
+vmCvar_t ui_loading;
+vmCvar_t ui_voteActive;
+vmCvar_t ui_alienTeamVoteActive;
+vmCvar_t ui_humanTeamVoteActive;
+
+vmCvar_t cg_debugRandom;
+
+vmCvar_t cg_optimizePrediction;
+vmCvar_t cg_projectileNudge;
+vmCvar_t cg_unlagged;
+
+
+typedef struct
+{
+ vmCvar_t *vmCvar;
+ char *cvarName;
+ char *defaultString;
+ int cvarFlags;
+} cvarTable_t;
+
+static cvarTable_t cvarTable[ ] =
+{
+ { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging
+ { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE },
+ { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE },
+ { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE },
+ { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE },
+ { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE },
+ { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE },
+ { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE },
+ { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE },
+ { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE },
+ { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE },
+ { &cg_drawTimer, "cg_drawTimer", "1", CVAR_ARCHIVE },
+ { &cg_drawClock, "cg_drawClock", "0", CVAR_ARCHIVE },
+ { &cg_drawFPS, "cg_drawFPS", "1", CVAR_ARCHIVE },
+ { &cg_drawDemoState, "cg_drawDemoState", "1", CVAR_ARCHIVE },
+ { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE },
+ { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE },
+ { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE },
+ { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE },
+ { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE },
+ { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE },
+ { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE },
+ { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE },
+ { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE },
+ { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE },
+ { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE },
+ { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE },
+ { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE },
+ { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE },
+ { &cg_teslaTrailTime, "cg_teslaTrailTime", "250", CVAR_ARCHIVE },
+ { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE },
+ { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT },
+ { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT },
+ { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT },
+ { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT },
+ { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE},
+ { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE },
+ { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT },
+ { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE },
+ { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE },
+ { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT },
+ { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT },
+ { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT },
+ { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT },
+ { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT },
+ { &cg_errorDecay, "cg_errordecay", "100", 0 },
+ { &cg_nopredict, "cg_nopredict", "0", 0 },
+ { &cg_debugMove, "cg_debugMove", "0", 0 },
+ { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT },
+ { &cg_showmiss, "cg_showmiss", "0", 0 },
+ { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT },
+ { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT },
+ { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT },
+ { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT },
+ { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT },
+ { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT },
+ { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT },
+ { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE },
+ { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE },
+ { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE },
+ { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE },
+ { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO },
+ { &cg_stats, "cg_stats", "0", 0 },
+ { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE },
+ { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE },
+ { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE },
+ { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE },
+ { &cg_creepRes, "cg_creepRes", "16", CVAR_ARCHIVE },
+ { &cg_drawSurfNormal, "cg_drawSurfNormal", "0", CVAR_CHEAT },
+ { &cg_drawBBOX, "cg_drawBBOX", "0", CVAR_CHEAT },
+ { &cg_debugAlloc, "cg_debugAlloc", "0", 0 },
+ { &cg_wwSmoothTime, "cg_wwSmoothTime", "300", CVAR_ARCHIVE },
+ { &cg_wwFollow, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO },
+ { &cg_wwToggle, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO },
+ { &cg_unlagged, "cg_unlagged", "1", CVAR_ARCHIVE|CVAR_USERINFO },
+ { &cg_depthSortParticles, "cg_depthSortParticles", "1", CVAR_ARCHIVE },
+ { &cg_bounceParticles, "cg_bounceParticles", "0", CVAR_ARCHIVE },
+ { &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE },
+ { &cg_lightFlare, "cg_lightFlare", "3", CVAR_ARCHIVE },
+ { &cg_debugParticles, "cg_debugParticles", "0", CVAR_CHEAT },
+ { &cg_debugTrails, "cg_debugTrails", "0", CVAR_CHEAT },
+ { &cg_debugPVS, "cg_debugPVS", "0", CVAR_CHEAT },
+ { &cg_disableWarningDialogs, "cg_disableWarningDialogs", "0", CVAR_ARCHIVE },
+ { &cg_disableScannerPlane, "cg_disableScannerPlane", "0", CVAR_ARCHIVE },
+ { &cg_tutorial, "cg_tutorial", "1", CVAR_ARCHIVE },
+ { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE},
+
+ { &cg_painBlendUpRate, "cg_painBlendUpRate", "10.0", 0 },
+ { &cg_painBlendDownRate, "cg_painBlendDownRate", "0.5", 0 },
+ { &cg_painBlendMax, "cg_painBlendMax", "0.7", 0 },
+ { &cg_painBlendScale, "cg_painBlendScale", "7.0", 0 },
+ { &cg_painBlendZoom, "cg_painBlendZoom", "0.65", 0 },
+
+ { &ui_currentClass, "ui_currentClass", "0", 0 },
+ { &ui_carriage, "ui_carriage", "", 0 },
+ { &ui_stages, "ui_stages", "0 0", 0 },
+ { &ui_dialog, "ui_dialog", "Text not set", 0 },
+ { &ui_loading, "ui_loading", "0", 0 },
+ { &ui_voteActive, "ui_voteActive", "0", 0 },
+ { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", 0 },
+ { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", 0 },
+
+ { &cg_debugRandom, "cg_debugRandom", "0", 0 },
+
+ { &cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE },
+ { &cg_projectileNudge, "cg_projectileNudge", "1", CVAR_ARCHIVE },
+
+ // the following variables are created in other parts of the system,
+ // but we also reference them here
+
+ { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures
+ { &cg_paused, "cl_paused", "0", CVAR_ROM },
+ { &cg_blood, "com_blood", "1", CVAR_ARCHIVE },
+ { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo
+ { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT},
+ { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE},
+ { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0},
+ { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0},
+ { &cg_timescale, "timescale", "1", 0},
+ { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE},
+ { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE},
+ { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT},
+
+ { &pmove_fixed, "pmove_fixed", "0", 0},
+ { &pmove_msec, "pmove_msec", "8", 0},
+ { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE},
+ { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE},
+ { &cg_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE},
+ { &cg_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE},
+ { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE},
+ { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE},
+ { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE},
+ { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE}
+// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE }
+};
+
+static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] );
+
+/*
+=================
+CG_RegisterCvars
+=================
+*/
+void CG_RegisterCvars( void )
+{
+ int i;
+ cvarTable_t *cv;
+ char var[ MAX_TOKEN_CHARS ];
+
+ for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ )
+ {
+ trap_Cvar_Register( cv->vmCvar, cv->cvarName,
+ cv->defaultString, cv->cvarFlags );
+ }
+
+ //repress standard Q3 console
+ trap_Cvar_Set( "con_notifytime", "-2" );
+
+ // see if we are also running the server on this machine
+ trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) );
+ cgs.localServer = atoi( var );
+ forceModelModificationCount = cg_forceModel.modificationCount;
+
+ trap_Cvar_Register( NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE );
+ trap_Cvar_Register( NULL, "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 );
+}
+
+
+/*
+===================
+CG_ForceModelChange
+===================
+*/
+static void CG_ForceModelChange( void )
+{
+ int i;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ const char *clientInfo;
+
+ clientInfo = CG_ConfigString( CS_PLAYERS + i );
+
+ if( !clientInfo[ 0 ] )
+ continue;
+
+ CG_NewClientInfo( i );
+ }
+}
+
+/*
+===============
+CG_SetPVars
+
+Set the p_* cvars
+===============
+*/
+static void CG_SetPVars( void )
+{
+ playerState_t *ps;
+
+ if( !cg.snap )
+ return;
+
+ ps = &cg.snap->ps;
+
+ trap_Cvar_Set( "player_hp", va( "%d", ps->stats[ STAT_HEALTH ] ) );
+ trap_Cvar_Set( "player_maxhp", va( "%d", ps->stats[ STAT_MAX_HEALTH ] ) );
+ switch( ps->stats[ STAT_PTEAM ] )
+ {
+ case PTE_NONE:
+ trap_Cvar_Set( "player_team", "spectator" );
+ trap_Cvar_Set( "player_stage", "0" );
+ trap_Cvar_Set( "player_spawns","0" );
+ trap_Cvar_Set( "player_kns", "0" );
+ trap_Cvar_Set( "player_bp", "0" );
+ trap_Cvar_Set( "player_maxbp", "0" );
+ break;
+
+ case PTE_ALIENS:
+ trap_Cvar_Set( "player_team", "alien" );
+ trap_Cvar_Set( "player_stage", va( "%d", cgs.alienStage+1 ) );
+ trap_Cvar_Set( "player_spawns",va( "%d", cgs.numAlienSpawns ));
+ trap_Cvar_Set( "player_kns", va( "%d",((cgs.alienStage==2)?0:abs(cgs.alienNextStageThreshold-cgs.alienKills))));
+ trap_Cvar_Set( "player_stage", va( "%d", cgs.alienStage+1 ) );
+ trap_Cvar_Set( "player_bp", va( "%d", cgs.alienBuildPoints ));
+ trap_Cvar_Set( "player_maxbp", va( "%d", cgs.alienBuildPointsTotal ));
+ break;
+
+ case PTE_HUMANS:
+ trap_Cvar_Set( "player_team", "human" );
+ trap_Cvar_Set( "player_stage", va( "%d", cgs.humanStage+1 ) );
+ trap_Cvar_Set( "player_spawns",va( "%d", cgs.numHumanSpawns ));
+ trap_Cvar_Set( "player_kns", va( "%d",((cgs.humanStage==2)?0:abs(cgs.humanNextStageThreshold-cgs.humanKills))));
+ trap_Cvar_Set( "player_bp", va( "%d", cgs.humanBuildPoints ));
+ trap_Cvar_Set( "player_maxbp", va( "%d", cgs.humanBuildPointsTotal ));
+ break;
+ }
+
+ trap_Cvar_Set( "player_credits", va( "%d", ps->persistant[ PERS_CREDIT ] ) );
+ trap_Cvar_Set( "player_score", va( "%d", ps->persistant[ PERS_SCORE ] ) );
+ trap_Cvar_Set( "player_deaths", va( "%d", ps->persistant[ PERS_KILLED ] ) );
+
+ if ( CG_LastAttacker( ) != -1 )
+ {
+ trap_Cvar_Set( "player_attacker", cgs.clientinfo[ CG_LastAttacker( ) ].name );
+ trap_Cvar_Set( "player_attacker_hp", va( "%d", cgs.clientinfo[ CG_LastAttacker( ) ].health));
+ }
+ else
+ {
+ trap_Cvar_Set( "player_attacker", "" );
+ trap_Cvar_Set( "player_attacker_hp", "" );
+ }
+
+
+ if ( CG_CrosshairPlayer( ) != -1 )
+ {
+ trap_Cvar_Set( "player_crosshair", cgs.clientinfo[ CG_CrosshairPlayer( ) ].name );
+ //XXX hax required
+ //trap_Cvar_Set( "player_crosshair_credits", va("%d",cgs.clientinfo[CG_CrosshairPlayer( )].credits));
+ }
+ else
+ {
+ trap_Cvar_Set( "player_crosshair", "" );
+ //trap_Cvar_Set( "player_crosshair_credits", "" );
+ }
+
+ // stages
+ trap_Cvar_Set( "alien_stage", va( "%d", cgs.alienStage+1 ) );
+ trap_Cvar_Set( "human_stage", va( "%d", cgs.humanStage+1 ) );
+
+ // alien kills to next stage
+ if( cgs.alienStage == 2 )
+ trap_Cvar_Set( "alien_kns", va( "%d", 0 ) );
+ else
+ trap_Cvar_Set( "alien_kns", va( "%d", abs(cgs.alienNextStageThreshold - cgs.alienKills)) );
+
+ // human kills to next stage
+ if( cgs.humanStage == 2 )
+ trap_Cvar_Set( "human_kns", va( "%d", 0 ) );
+ else
+ trap_Cvar_Set( "human_kns", va( "%d", abs(cgs.humanNextStageThreshold - cgs.humanKills)) );
+
+ // General score information
+ trap_Cvar_Set( "alien_score", va( "%d", cgs.alienKills ) );
+ trap_Cvar_Set( "human_score", va( "%d", cgs.humanKills ) );
+
+ // class type
+ switch ( ps->stats[ STAT_PCLASS ] )
+ {
+ case PCL_ALIEN_BUILDER0:
+ trap_Cvar_Set( "player_class", "Granger" );
+ trap_Cvar_Set( "player_weapon", "Granger" );
+ break;
+
+ case PCL_ALIEN_BUILDER0_UPG:
+ trap_Cvar_Set( "player_class", "Advanced Granger" );
+ trap_Cvar_Set( "player_weapon", "Advanced Granger" );
+ break;
+
+ case PCL_ALIEN_LEVEL0:
+ trap_Cvar_Set( "player_class", "Dretch" );
+ trap_Cvar_Set( "player_weapon", "Dretch" );
+ break;
+
+ case PCL_ALIEN_LEVEL1:
+ trap_Cvar_Set( "player_class", "Basilisk" );
+ trap_Cvar_Set( "player_weapon", "Basilisk" );
+ break;
+
+ case PCL_ALIEN_LEVEL1_UPG:
+ trap_Cvar_Set( "player_class", "Advanced Basilisk" );
+ trap_Cvar_Set( "player_weapon", "Advanced Basilisk" );
+ break;
+
+ case PCL_ALIEN_LEVEL2:
+ trap_Cvar_Set( "player_class", "Marauder" );
+ trap_Cvar_Set( "player_weapon", "Marauder" );
+ break;
+
+ case PCL_ALIEN_LEVEL2_UPG:
+ trap_Cvar_Set( "player_class", "Advanced Marauder" );
+ trap_Cvar_Set( "player_weapon", "Advanced Maruder" );
+ break;
+
+ case PCL_ALIEN_LEVEL3:
+ trap_Cvar_Set( "player_class", "Dragoon" );
+ trap_Cvar_Set( "player_weapon", "Dragoon" );
+ break;
+
+ case PCL_ALIEN_LEVEL3_UPG:
+ trap_Cvar_Set( "player_class", "Advanced Dragoon" );
+ trap_Cvar_Set( "player_weapon", "Advanced Dragoon" );
+ break;
+
+ case PCL_ALIEN_LEVEL4:
+ trap_Cvar_Set( "player_class", "Tyrant" );
+ trap_Cvar_Set( "player_weapon", "Tyrant" );
+ break;
+
+ case PCL_HUMAN:
+ trap_Cvar_Set( "player_class", "Human" );
+ break;
+
+ case PCL_HUMAN_BSUIT:
+ trap_Cvar_Set( "player_class", "Battlesuit" );
+ break;
+
+ default:
+ trap_Cvar_Set( "player_class", "Unknown" );
+ }
+
+ // weapons
+ switch ( ps->weapon )
+ {
+ case WP_HBUILD:
+ trap_Cvar_Set( "player_weapon", "Construction Kit" );
+ break;
+
+ case WP_HBUILD2:
+ trap_Cvar_Set( "player_weapon", "Advanced Construction Kit" );
+ break;
+
+ case WP_BLASTER:
+ trap_Cvar_Set( "player_weapon", "Blaster" );
+ break;
+
+ case WP_MACHINEGUN:
+ trap_Cvar_Set( "player_weapon", "Machine Gun" );
+ break;
+
+ case WP_PAIN_SAW:
+ trap_Cvar_Set( "player_weapon", "Painsaw" );
+ break;
+
+ case WP_SHOTGUN:
+ trap_Cvar_Set( "player_weapon", "Shotgun" );
+ break;
+
+ case WP_LAS_GUN:
+ trap_Cvar_Set( "player_weapon", "Laser Gun" );
+ break;
+
+ case WP_MASS_DRIVER:
+ trap_Cvar_Set( "player_weapon", "Mass Driver" );
+ break;
+
+ case WP_CHAINGUN:
+ trap_Cvar_Set( "player_weapon", "Chain Gun" );
+ break;
+
+ case WP_PULSE_RIFLE:
+ trap_Cvar_Set( "player_weapon", "Pulse Rifle" );
+ break;
+
+ case WP_FLAMER:
+ trap_Cvar_Set( "player_weapon", "Flame Thrower" );
+ break;
+
+ case WP_LUCIFER_CANNON:
+ trap_Cvar_Set( "player_weapon", "Lucifier cannon" );
+ break;
+
+ case WP_GRENADE:
+ trap_Cvar_Set( "player_weapon", "Grenade" );
+ break;
+
+ default:
+ trap_Cvar_Set( "player_weapon", "Unknown" );
+ }
+}
+
+/*
+=================
+CG_UpdateCvars
+=================
+*/
+void CG_UpdateCvars( void )
+{
+ int i;
+ cvarTable_t *cv;
+
+ CG_SetPVars();
+
+ for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ )
+ trap_Cvar_Update( cv->vmCvar );
+
+ // check for modications here
+
+ // if force model changed
+ if( forceModelModificationCount != cg_forceModel.modificationCount )
+ {
+ forceModelModificationCount = cg_forceModel.modificationCount;
+ CG_ForceModelChange( );
+ }
+}
+
+
+int CG_CrosshairPlayer( void )
+{
+ if( cg.time > ( cg.crosshairClientTime + 1000 ) )
+ return -1;
+
+ return cg.crosshairClientNum;
+}
+
+
+int CG_LastAttacker( void )
+{
+ if( !cg.attackerTime )
+ return -1;
+
+ return cg.snap->ps.persistant[ PERS_ATTACKER ];
+}
+
+/*
+=================
+CG_RemoveNotifyLine
+=================
+*/
+void CG_RemoveNotifyLine( void )
+{
+ int i, offset, totalLength;
+
+ if( cg.numConsoleLines == 0 )
+ return;
+
+ offset = cg.consoleLines[ 0 ].length;
+ totalLength = strlen( cg.consoleText ) - offset;
+
+ //slide up consoleText
+ for( i = 0; i <= totalLength; i++ )
+ cg.consoleText[ i ] = cg.consoleText[ i + offset ];
+
+ //pop up the first consoleLine
+ for( i = 0; i < cg.numConsoleLines; i++ )
+ cg.consoleLines[ i ] = cg.consoleLines[ i + 1 ];
+
+ cg.numConsoleLines--;
+}
+
+/*
+=================
+CG_AddNotifyText
+=================
+*/
+void CG_AddNotifyText( void )
+{
+ char buffer[ BIG_INFO_STRING ];
+
+ trap_LiteralArgs( buffer, BIG_INFO_STRING );
+
+ if( !buffer[ 0 ] )
+ {
+ cg.consoleText[ 0 ] = '\0';
+ cg.numConsoleLines = 0;
+ return;
+ }
+
+ if( cg.numConsoleLines == MAX_CONSOLE_LINES )
+ CG_RemoveNotifyLine( );
+
+ Q_strcat( cg.consoleText, MAX_CONSOLE_TEXT, buffer );
+ cg.consoleLines[ cg.numConsoleLines ].time = cg.time;
+ cg.consoleLines[ cg.numConsoleLines ].length = strlen( buffer );
+ cg.numConsoleLines++;
+}
+
+void QDECL CG_Printf( const char *msg, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, msg );
+ vsprintf( text, msg, argptr );
+ va_end( argptr );
+
+ trap_Print( text );
+}
+
+void QDECL CG_Error( const char *msg, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, msg );
+ vsprintf( text, msg, argptr );
+ va_end( argptr );
+
+ trap_Error( text );
+}
+
+void QDECL Com_Error( int level, const char *error, ... )
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start( argptr, error );
+ vsprintf( text, error, argptr );
+ va_end( argptr );
+
+ CG_Error( "%s", text );
+}
+
+void QDECL Com_Printf( const char *msg, ... ) {
+ va_list argptr;
+ char text[1024];
+
+ va_start (argptr, msg);
+ vsprintf (text, msg, argptr);
+ va_end (argptr);
+
+ CG_Printf ("%s", text);
+}
+
+
+
+/*
+================
+CG_Argv
+================
+*/
+const char *CG_Argv( int arg )
+{
+ static char buffer[ MAX_STRING_CHARS ];
+
+ trap_Argv( arg, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+
+//========================================================================
+
+/*
+=================
+CG_FileExists
+
+Test if a specific file exists or not
+=================
+*/
+qboolean CG_FileExists( char *filename )
+{
+ fileHandle_t f;
+
+ if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 )
+ {
+ //file exists so close it
+ trap_FS_FCloseFile( f );
+
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+/*
+=================
+CG_RegisterSounds
+
+called during a precache command
+=================
+*/
+static void CG_RegisterSounds( void )
+{
+ int i;
+ char name[ MAX_QPATH ];
+ const char *soundName;
+
+ cgs.media.alienStageTransition = trap_S_RegisterSound( "sound/announcements/overmindevolved.wav", qtrue );
+ cgs.media.humanStageTransition = trap_S_RegisterSound( "sound/announcements/reinforcement.wav", qtrue );
+
+ cgs.media.alienOvermindAttack = trap_S_RegisterSound( "sound/announcements/overmindattack.wav", qtrue );
+ cgs.media.alienOvermindDying = trap_S_RegisterSound( "sound/announcements/overminddying.wav", qtrue );
+ cgs.media.alienOvermindSpawns = trap_S_RegisterSound( "sound/announcements/overmindspawns.wav", qtrue );
+
+ cgs.media.alienL1Grab = trap_S_RegisterSound( "sound/player/level1/grab.wav", qtrue );
+ cgs.media.alienL4ChargePrepare = trap_S_RegisterSound( "sound/player/level4/charge_prepare.wav", qtrue );
+ cgs.media.alienL4ChargeStart = trap_S_RegisterSound( "sound/player/level4/charge_start.wav", qtrue );
+
+ cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/tracer.wav", qfalse );
+ cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse );
+
+ cgs.media.talkSound = trap_S_RegisterSound( "sound/misc/talk.wav", qfalse );
+ cgs.media.alienTalkSound = trap_S_RegisterSound( "sound/misc/alien_talk.wav", qfalse );
+ cgs.media.humanTalkSound = trap_S_RegisterSound( "sound/misc/human_talk.wav", qfalse );
+ cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse );
+
+ cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse );
+ cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse );
+ cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse );
+
+ cgs.media.disconnectSound = trap_S_RegisterSound( "sound/misc/disconnect.wav", qfalse );
+
+ for( i = 0; i < 4; i++ )
+ {
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/step%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_NORMAL ][ i ] = trap_S_RegisterSound( name, qfalse );
+
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/flesh%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_FLESH ][ i ] = trap_S_RegisterSound( name, qfalse );
+
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/splash%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_SPLASH ][ i ] = trap_S_RegisterSound( name, qfalse );
+
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/clank%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_METAL ][ i ] = trap_S_RegisterSound( name, qfalse );
+ }
+
+ for( i = 1 ; i < MAX_SOUNDS ; i++ )
+ {
+ soundName = CG_ConfigString( CS_SOUNDS + i );
+
+ if( !soundName[ 0 ] )
+ break;
+
+ if( soundName[ 0 ] == '*' )
+ continue; // custom sound
+
+ cgs.gameSounds[ i ] = trap_S_RegisterSound( soundName, qfalse );
+ }
+
+ cgs.media.jetpackDescendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/low.wav", qfalse );
+ cgs.media.jetpackIdleSound = trap_S_RegisterSound( "sound/upgrades/jetpack/idle.wav", qfalse );
+ cgs.media.jetpackAscendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/hi.wav", qfalse );
+
+ cgs.media.medkitUseSound = trap_S_RegisterSound( "sound/upgrades/medkit/medkit.wav", qfalse );
+
+ cgs.media.alienEvolveSound = trap_S_RegisterSound( "sound/player/alienevolve.wav", qfalse );
+
+ cgs.media.alienBuildableExplosion = trap_S_RegisterSound( "sound/buildables/alien/explosion.wav", qfalse );
+ cgs.media.alienBuildableDamage = trap_S_RegisterSound( "sound/buildables/alien/damage.wav", qfalse );
+ cgs.media.alienBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/alien/prebuild.wav", qfalse );
+
+ cgs.media.humanBuildableExplosion = trap_S_RegisterSound( "sound/buildables/human/explosion.wav", qfalse );
+ cgs.media.humanBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/human/prebuild.wav", qfalse );
+
+ for( i = 0; i < 4; i++ )
+ cgs.media.humanBuildableDamage[ i ] = trap_S_RegisterSound(
+ va( "sound/buildables/human/damage%d.wav", i ), qfalse );
+
+ cgs.media.hardBounceSound1 = trap_S_RegisterSound( "sound/misc/hard_bounce1.wav", qfalse );
+ cgs.media.hardBounceSound2 = trap_S_RegisterSound( "sound/misc/hard_bounce2.wav", qfalse );
+
+ cgs.media.repeaterUseSound = trap_S_RegisterSound( "sound/buildables/repeater/use.wav", qfalse );
+
+ cgs.media.buildableRepairSound = trap_S_RegisterSound( "sound/buildables/human/repair.wav", qfalse );
+ cgs.media.buildableRepairedSound = trap_S_RegisterSound( "sound/buildables/human/repaired.wav", qfalse );
+
+ cgs.media.lCannonWarningSound = trap_S_RegisterSound( "models/weapons/lcannon/warning.wav", qfalse );
+}
+
+
+//===================================================================================
+
+
+/*
+=================
+CG_RegisterGraphics
+
+This function may execute for a couple of minutes with a slow disk.
+=================
+*/
+static void CG_RegisterGraphics( void )
+{
+ int i;
+ static char *sb_nums[ 11 ] =
+ {
+ "gfx/2d/numbers/zero_32b",
+ "gfx/2d/numbers/one_32b",
+ "gfx/2d/numbers/two_32b",
+ "gfx/2d/numbers/three_32b",
+ "gfx/2d/numbers/four_32b",
+ "gfx/2d/numbers/five_32b",
+ "gfx/2d/numbers/six_32b",
+ "gfx/2d/numbers/seven_32b",
+ "gfx/2d/numbers/eight_32b",
+ "gfx/2d/numbers/nine_32b",
+ "gfx/2d/numbers/minus_32b",
+ };
+ static char *buildWeaponTimerPieShaders[ 8 ] =
+ {
+ "ui/assets/neutral/1_5pie",
+ "ui/assets/neutral/3_0pie",
+ "ui/assets/neutral/4_5pie",
+ "ui/assets/neutral/6_0pie",
+ "ui/assets/neutral/7_5pie",
+ "ui/assets/neutral/9_0pie",
+ "ui/assets/neutral/10_5pie",
+ "ui/assets/neutral/12_0pie",
+ };
+
+ // clear any references to old media
+ memset( &cg.refdef, 0, sizeof( cg.refdef ) );
+ trap_R_ClearScene( );
+
+ trap_R_LoadWorldMap( cgs.mapname );
+ CG_UpdateMediaFraction( 0.66f );
+
+ for( i = 0; i < 11; i++ )
+ cgs.media.numberShaders[ i ] = trap_R_RegisterShader( sb_nums[ i ] );
+
+ cgs.media.viewBloodShader = trap_R_RegisterShader( "gfx/damage/fullscreen_painblend" );
+
+ cgs.media.connectionShader = trap_R_RegisterShader( "gfx/2d/net" );
+
+ cgs.media.creepShader = trap_R_RegisterShader( "creep" );
+
+ cgs.media.scannerBlipShader = trap_R_RegisterShader( "gfx/2d/blip" );
+ cgs.media.scannerLineShader = trap_R_RegisterShader( "gfx/2d/stalk" );
+
+ cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" );
+
+ cgs.media.backTileShader = trap_R_RegisterShader( "console" );
+
+
+ //TA: building shaders
+ cgs.media.greenBuildShader = trap_R_RegisterShader("gfx/misc/greenbuild" );
+ cgs.media.redBuildShader = trap_R_RegisterShader("gfx/misc/redbuild" );
+ cgs.media.humanSpawningShader = trap_R_RegisterShader("models/buildables/telenode/rep_cyl" );
+
+ for( i = 0; i < 8; i++ )
+ cgs.media.buildWeaponTimerPie[ i ] = trap_R_RegisterShader( buildWeaponTimerPieShaders[ i ] );
+
+ cgs.media.upgradeClassIconShader = trap_R_RegisterShader( "icons/icona_upgrade.tga" );
+
+ cgs.media.balloonShader = trap_R_RegisterShader( "gfx/sprites/chatballoon" );
+
+ cgs.media.disconnectPS = CG_RegisterParticleSystem( "disconnectPS" );
+
+ CG_UpdateMediaFraction( 0.7f );
+
+ memset( cg_weapons, 0, sizeof( cg_weapons ) );
+ memset( cg_upgrades, 0, sizeof( cg_upgrades ) );
+
+ cgs.media.shadowMarkShader = trap_R_RegisterShader( "gfx/marks/shadow" );
+ cgs.media.wakeMarkShader = trap_R_RegisterShader( "gfx/marks/wake" );
+
+ cgs.media.poisonCloudPS = CG_RegisterParticleSystem( "firstPersonPoisonCloudPS" );
+ cgs.media.alienEvolvePS = CG_RegisterParticleSystem( "alienEvolvePS" );
+ cgs.media.alienAcidTubePS = CG_RegisterParticleSystem( "alienAcidTubePS" );
+
+ cgs.media.jetPackDescendPS = CG_RegisterParticleSystem( "jetPackDescendPS" );
+ cgs.media.jetPackHoverPS = CG_RegisterParticleSystem( "jetPackHoverPS" );
+ cgs.media.jetPackAscendPS = CG_RegisterParticleSystem( "jetPackAscendPS" );
+
+ cgs.media.humanBuildableDamagedPS = CG_RegisterParticleSystem( "humanBuildableDamagedPS" );
+ cgs.media.alienBuildableDamagedPS = CG_RegisterParticleSystem( "alienBuildableDamagedPS" );
+ cgs.media.humanBuildableDestroyedPS = CG_RegisterParticleSystem( "humanBuildableDestroyedPS" );
+ cgs.media.alienBuildableDestroyedPS = CG_RegisterParticleSystem( "alienBuildableDestroyedPS" );
+
+ cgs.media.alienBleedPS = CG_RegisterParticleSystem( "alienBleedPS" );
+ cgs.media.humanBleedPS = CG_RegisterParticleSystem( "humanBleedPS" );
+
+ CG_BuildableStatusParse( "ui/assets/human/buildstat.cfg", &cgs.humanBuildStat );
+ CG_BuildableStatusParse( "ui/assets/alien/buildstat.cfg", &cgs.alienBuildStat );
+
+ // register the inline models
+ cgs.numInlineModels = trap_CM_NumInlineModels( );
+
+ for( i = 1; i < cgs.numInlineModels; i++ )
+ {
+ char name[ 10 ];
+ vec3_t mins, maxs;
+ int j;
+
+ Com_sprintf( name, sizeof( name ), "*%i", i );
+
+ cgs.inlineDrawModel[ i ] = trap_R_RegisterModel( name );
+ trap_R_ModelBounds( cgs.inlineDrawModel[ i ], mins, maxs );
+
+ for( j = 0 ; j < 3 ; j++ )
+ cgs.inlineModelMidpoints[ i ][ j ] = mins[ j ] + 0.5 * ( maxs[ j ] - mins[ j ] );
+ }
+
+ // register all the server specified models
+ for( i = 1; i < MAX_MODELS; i++ )
+ {
+ const char *modelName;
+
+ modelName = CG_ConfigString( CS_MODELS + i );
+
+ if( !modelName[ 0 ] )
+ break;
+
+ cgs.gameModels[ i ] = trap_R_RegisterModel( modelName );
+ }
+
+ CG_UpdateMediaFraction( 0.8f );
+
+ // register all the server specified shaders
+ for( i = 1; i < MAX_GAME_SHADERS; i++ )
+ {
+ const char *shaderName;
+
+ shaderName = CG_ConfigString( CS_SHADERS + i );
+
+ if( !shaderName[ 0 ] )
+ break;
+
+ cgs.gameShaders[ i ] = trap_R_RegisterShader( shaderName );
+ }
+
+ CG_UpdateMediaFraction( 0.9f );
+
+ // register all the server specified particle systems
+ for( i = 1; i < MAX_GAME_PARTICLE_SYSTEMS; i++ )
+ {
+ const char *psName;
+
+ psName = CG_ConfigString( CS_PARTICLE_SYSTEMS + i );
+
+ if( !psName[ 0 ] )
+ break;
+
+ cgs.gameParticleSystems[ i ] = CG_RegisterParticleSystem( (char *)psName );
+ }
+}
+
+
+/*
+=======================
+CG_BuildSpectatorString
+
+=======================
+*/
+void CG_BuildSpectatorString( void )
+{
+ int i;
+
+ cg.spectatorList[ 0 ] = 0;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == PTE_NONE )
+ Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), va( "%s " S_COLOR_WHITE, cgs.clientinfo[ i ].name ) );
+ }
+
+ i = strlen( cg.spectatorList );
+
+ if( i != cg.spectatorLen )
+ {
+ cg.spectatorLen = i;
+ cg.spectatorWidth = -1;
+ }
+}
+
+
+
+/*
+===================
+CG_RegisterClients
+
+===================
+*/
+static void CG_RegisterClients( void )
+{
+ int i;
+
+ cg.charModelFraction = 0.0f;
+
+ //precache all the models/sounds/etc
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ CG_PrecacheClientInfo( i, BG_FindModelNameForClass( i ),
+ BG_FindSkinNameForClass( i ) );
+
+ cg.charModelFraction = (float)i / (float)PCL_NUM_CLASSES;
+ trap_UpdateScreen( );
+ }
+
+ cgs.media.larmourHeadSkin = trap_R_RegisterSkin( "models/players/human_base/head_light.skin" );
+ cgs.media.larmourLegsSkin = trap_R_RegisterSkin( "models/players/human_base/lower_light.skin" );
+ cgs.media.larmourTorsoSkin = trap_R_RegisterSkin( "models/players/human_base/upper_light.skin" );
+
+ cgs.media.jetpackModel = trap_R_RegisterModel( "models/players/human_base/jetpack.md3" );
+ cgs.media.jetpackFlashModel = trap_R_RegisterModel( "models/players/human_base/jetpack_flash.md3" );
+ cgs.media.battpackModel = trap_R_RegisterModel( "models/players/human_base/battpack.md3" );
+
+ cg.charModelFraction = 1.0f;
+ trap_UpdateScreen( );
+
+ //load all the clientinfos of clients already connected to the server
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ const char *clientInfo;
+
+ clientInfo = CG_ConfigString( CS_PLAYERS + i );
+ if( !clientInfo[ 0 ] )
+ continue;
+
+ CG_NewClientInfo( i );
+ }
+
+ CG_BuildSpectatorString( );
+}
+
+//===========================================================================
+
+/*
+=================
+CG_ConfigString
+=================
+*/
+const char *CG_ConfigString( int index )
+{
+ if( index < 0 || index >= MAX_CONFIGSTRINGS )
+ CG_Error( "CG_ConfigString: bad index: %i", index );
+
+ return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ];
+}
+
+//==================================================================
+
+/*
+======================
+CG_StartMusic
+
+======================
+*/
+void CG_StartMusic( void )
+{
+ char *s;
+ char parm1[ MAX_QPATH ], parm2[ MAX_QPATH ];
+
+ // start the background music
+ s = (char *)CG_ConfigString( CS_MUSIC );
+ Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) );
+ Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) );
+
+ trap_S_StartBackgroundTrack( parm1, parm2 );
+}
+
+/*
+======================
+CG_PlayerCount
+======================
+*/
+int CG_PlayerCount( void )
+{
+ int i, count = 0;
+
+ CG_RequestScores( );
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_ALIENS ||
+ cg.scores[ i ].team == PTE_HUMANS )
+ count++;
+ }
+
+ return count;
+}
+
+//
+// ==============================
+// new hud stuff ( mission pack )
+// ==============================
+//
+char *CG_GetMenuBuffer( const char *filename )
+{
+ int len;
+ fileHandle_t f;
+ static char buf[ MAX_MENUFILE ];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+
+ if( !f )
+ {
+ trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) );
+ return NULL;
+ }
+
+ if( len >= MAX_MENUFILE )
+ {
+ trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i",
+ filename, len, MAX_MENUFILE ) );
+ trap_FS_FCloseFile( f );
+ return NULL;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ return buf;
+}
+
+qboolean CG_Asset_Parse( int handle )
+{
+ pc_token_t token;
+ const char *tempStr;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "{" ) != 0 )
+ return qfalse;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "}" ) == 0 )
+ return qtrue;
+
+ // font
+ if( Q_stricmp( token.string, "font" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.textFont );
+ continue;
+ }
+
+ // smallFont
+ if( Q_stricmp( token.string, "smallFont" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.smallFont );
+ continue;
+ }
+
+ // font
+ if( Q_stricmp( token.string, "bigfont" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.bigFont );
+ continue;
+ }
+
+ // gradientbar
+ if( Q_stricmp( token.string, "gradientbar" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr );
+ continue;
+ }
+
+ // enterMenuSound
+ if( Q_stricmp( token.string, "menuEnterSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // exitMenuSound
+ if( Q_stricmp( token.string, "menuExitSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // itemFocusSound
+ if( Q_stricmp( token.string, "itemFocusSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // menuBuzzSound
+ if( Q_stricmp( token.string, "menuBuzzSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "cursor" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &cgDC.Assets.cursorStr ) )
+ return qfalse;
+
+ cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr );
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "fadeClamp" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.fadeClamp ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "fadeCycle" ) == 0 )
+ {
+ if( !PC_Int_Parse( handle, &cgDC.Assets.fadeCycle ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "fadeAmount" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.fadeAmount ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "shadowX" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.shadowX ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "shadowY" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.shadowY ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "shadowColor" ) == 0 )
+ {
+ if( !PC_Color_Parse( handle, &cgDC.Assets.shadowColor ) )
+ return qfalse;
+
+ cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[ 3 ];
+ continue;
+ }
+ }
+
+ return qfalse; // bk001204 - why not?
+}
+
+void CG_ParseMenu( const char *menuFile )
+{
+ pc_token_t token;
+ int handle;
+
+ handle = trap_Parse_LoadSource( menuFile );
+
+ if( !handle )
+ handle = trap_Parse_LoadSource( "ui/testhud.menu" );
+
+ if( !handle )
+ return;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ //if ( Q_stricmp( token, "{" ) ) {
+ // Com_Printf( "Missing { in menu file\n" );
+ // break;
+ //}
+
+ //if ( menuCount == MAX_MENUS ) {
+ // Com_Printf( "Too many menus!\n" );
+ // break;
+ //}
+
+ if( token.string[ 0 ] == '}' )
+ break;
+
+ if( Q_stricmp( token.string, "assetGlobalDef" ) == 0 )
+ {
+ if( CG_Asset_Parse( handle ) )
+ continue;
+ else
+ break;
+ }
+
+
+ if( Q_stricmp( token.string, "menudef" ) == 0 )
+ {
+ // start a new menu
+ Menu_New( handle );
+ }
+ }
+
+ trap_Parse_FreeSource( handle );
+}
+
+qboolean CG_Load_Menu( char **p )
+{
+ char *token;
+
+ token = COM_ParseExt( p, qtrue );
+
+ if( token[ 0 ] != '{' )
+ return qfalse;
+
+ while( 1 )
+ {
+ token = COM_ParseExt( p, qtrue );
+
+ if( Q_stricmp( token, "}" ) == 0 )
+ return qtrue;
+
+ if( !token || token[ 0 ] == 0 )
+ return qfalse;
+
+ CG_ParseMenu( token );
+ }
+ return qfalse;
+}
+
+
+
+void CG_LoadMenus( const char *menuFile )
+{
+ char *token;
+ char *p;
+ int len, start;
+ fileHandle_t f;
+ static char buf[ MAX_MENUDEFFILE ];
+
+ start = trap_Milliseconds( );
+
+ len = trap_FS_FOpenFile( menuFile, &f, FS_READ );
+
+ if( !f )
+ {
+ trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) );
+ len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ );
+
+ if( !f )
+ trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n" ) );
+ }
+
+ if( len >= MAX_MENUDEFFILE )
+ {
+ trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i",
+ menuFile, len, MAX_MENUDEFFILE ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ COM_Compress( buf );
+
+ Menu_Reset( );
+
+ p = buf;
+
+ while( 1 )
+ {
+ token = COM_ParseExt( &p, qtrue );
+
+ if( !token || token[ 0 ] == 0 || token[ 0 ] == '}' )
+ break;
+
+ if( Q_stricmp( token, "}" ) == 0 )
+ break;
+
+ if( Q_stricmp( token, "loadmenu" ) == 0 )
+ {
+ if( CG_Load_Menu( &p ) )
+ continue;
+ else
+ break;
+ }
+ }
+
+ Com_Printf( "UI menu load time = %d milli seconds\n", trap_Milliseconds( ) - start );
+}
+
+
+
+static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int flags, float *special, int key )
+{
+ return qfalse;
+}
+
+
+static int CG_FeederCount( float feederID )
+{
+ int i, count = 0;
+
+ if( feederID == FEEDER_ALIENTEAM_LIST )
+ {
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_ALIENS )
+ count++;
+ }
+ }
+ else if( feederID == FEEDER_HUMANTEAM_LIST )
+ {
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_HUMANS )
+ count++;
+ }
+ }
+
+ return count;
+}
+
+
+void CG_SetScoreSelection( void *p )
+{
+ menuDef_t *menu = (menuDef_t*)p;
+ playerState_t *ps = &cg.snap->ps;
+ int i, alien, human;
+ int feeder;
+
+ alien = human = 0;
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_ALIENS )
+ alien++;
+ else if( cg.scores[ i ].team == PTE_HUMANS )
+ human++;
+
+ if( ps->clientNum == cg.scores[ i ].client )
+ cg.selectedScore = i;
+ }
+
+ if( menu == NULL )
+ // just interested in setting the selected score
+ return;
+
+ feeder = FEEDER_ALIENTEAM_LIST;
+ i = alien;
+
+ if( cg.scores[ cg.selectedScore ].team == PTE_HUMANS )
+ {
+ feeder = FEEDER_HUMANTEAM_LIST;
+ i = human;
+ }
+
+ Menu_SetFeederSelection(menu, feeder, i, NULL);
+}
+
+// FIXME: might need to cache this info
+static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreIndex )
+{
+ int i, count;
+ count = 0;
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == team )
+ {
+ if( count == index )
+ {
+ *scoreIndex = i;
+ return &cgs.clientinfo[ cg.scores[ i ].client ];
+ }
+ count++;
+ }
+ }
+
+ *scoreIndex = index;
+ return &cgs.clientinfo[ cg.scores[ index ].client ];
+}
+
+static const char *CG_FeederItemText( float feederID, int index, int column, qhandle_t *handle )
+{
+ int scoreIndex = 0;
+ clientInfo_t *info = NULL;
+ int team = -1;
+ score_t *sp = NULL;
+ qboolean showIcons = qfalse;
+
+ *handle = -1;
+
+ if( feederID == FEEDER_ALIENTEAM_LIST )
+ team = PTE_ALIENS;
+ else if( feederID == FEEDER_HUMANTEAM_LIST )
+ team = PTE_HUMANS;
+
+ info = CG_InfoFromScoreIndex( index, team, &scoreIndex );
+ sp = &cg.scores[ scoreIndex ];
+
+ if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) &&
+ cg.intermissionStarted )
+ showIcons = qfalse;
+ else if( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.pm_flags & PMF_FOLLOW ||
+ team == cg.snap->ps.stats[ STAT_PTEAM ] || cg.intermissionStarted )
+ showIcons = qtrue;
+
+ if( info && info->infoValid )
+ {
+ switch( column )
+ {
+ case 0:
+ if( showIcons )
+ {
+ if( sp->weapon != WP_NONE )
+ *handle = cg_weapons[ sp->weapon ].weaponIcon;
+ }
+ break;
+
+ case 1:
+ if( showIcons )
+ {
+ if( sp->team == PTE_HUMANS && sp->upgrade != UP_NONE )
+ *handle = cg_upgrades[ sp->upgrade ].upgradeIcon;
+ else if( sp->team == PTE_ALIENS )
+ {
+ switch( sp->weapon )
+ {
+ case WP_ABUILD2:
+ case WP_ALEVEL1_UPG:
+ case WP_ALEVEL2_UPG:
+ case WP_ALEVEL3_UPG:
+ *handle = cgs.media.upgradeClassIconShader;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ break;
+
+ case 2:
+ if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) &&
+ cg.intermissionStarted )
+ return "Ready";
+ break;
+
+ case 3:
+ return info->name;
+ break;
+
+ case 4:
+ return va( "%d", info->score );
+ break;
+
+ case 5:
+ return va( "%4d", sp->time );
+ break;
+
+ case 6:
+ if( sp->ping == -1 )
+ return "connecting";
+
+ return va( "%4d", sp->ping );
+ break;
+ }
+ }
+
+ return "";
+}
+
+static qhandle_t CG_FeederItemImage( float feederID, int index )
+{
+ return 0;
+}
+
+static void CG_FeederSelection( float feederID, int index )
+{
+ int i, count;
+ int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? PTE_ALIENS : PTE_HUMANS;
+ count = 0;
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == team )
+ {
+ if( index == count )
+ cg.selectedScore = i;
+
+ count++;
+ }
+ }
+}
+
+static float CG_Cvar_Get( const char *cvar )
+{
+ char buff[ 128 ];
+
+ memset( buff, 0, sizeof( buff ) );
+ trap_Cvar_VariableStringBuffer( cvar, buff, sizeof( buff ) );
+ return atof( buff );
+}
+
+void CG_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text,
+ int cursorPos, char cursor, int limit, int style )
+{
+ CG_Text_Paint( x, y, scale, color, text, 0, limit, style );
+}
+
+static int CG_OwnerDrawWidth( int ownerDraw, float scale )
+{
+ switch( ownerDraw )
+ {
+ case CG_KILLER:
+ return CG_Text_Width( CG_GetKillerText( ), scale, 0 );
+ break;
+ }
+
+ return 0;
+}
+
+static int CG_PlayCinematic( const char *name, float x, float y, float w, float h )
+{
+ return trap_CIN_PlayCinematic( name, x, y, w, h, CIN_loop );
+}
+
+static void CG_StopCinematic( int handle )
+{
+ trap_CIN_StopCinematic( handle );
+}
+
+static void CG_DrawCinematic( int handle, float x, float y, float w, float h )
+{
+ trap_CIN_SetExtents( handle, x, y, w, h );
+ trap_CIN_DrawCinematic( handle );
+}
+
+static void CG_RunCinematicFrame( int handle )
+{
+ trap_CIN_RunCinematic( handle );
+}
+
+//TA: hack to prevent warning
+static qboolean CG_OwnerDrawVisible( int parameter )
+{
+ return qfalse;
+}
+
+/*
+=================
+CG_LoadHudMenu
+=================
+*/
+void CG_LoadHudMenu( void )
+{
+ char buff[ 1024 ];
+ const char *hudSet;
+
+ cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip;
+ cgDC.setColor = &trap_R_SetColor;
+ cgDC.drawHandlePic = &CG_DrawPic;
+ cgDC.drawStretchPic = &trap_R_DrawStretchPic;
+ cgDC.drawText = &CG_Text_Paint;
+ cgDC.textWidth = &CG_Text_Width;
+ cgDC.textHeight = &CG_Text_Height;
+ cgDC.registerModel = &trap_R_RegisterModel;
+ cgDC.modelBounds = &trap_R_ModelBounds;
+ cgDC.fillRect = &CG_FillRect;
+ cgDC.drawRect = &CG_DrawRect;
+ cgDC.drawSides = &CG_DrawSides;
+ cgDC.drawTopBottom = &CG_DrawTopBottom;
+ cgDC.clearScene = &trap_R_ClearScene;
+ cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene;
+ cgDC.renderScene = &trap_R_RenderScene;
+ cgDC.registerFont = &trap_R_RegisterFont;
+ cgDC.ownerDrawItem = &CG_OwnerDraw;
+ cgDC.getValue = &CG_GetValue;
+ cgDC.ownerDrawVisible = &CG_OwnerDrawVisible;
+ cgDC.runScript = &CG_RunMenuScript;
+ cgDC.getTeamColor = &CG_GetTeamColor;
+ cgDC.setCVar = trap_Cvar_Set;
+ cgDC.getCVarString = trap_Cvar_VariableStringBuffer;
+ cgDC.getCVarValue = CG_Cvar_Get;
+ cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor;
+ //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode;
+ //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode;
+ cgDC.startLocalSound = &trap_S_StartLocalSound;
+ cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey;
+ cgDC.feederCount = &CG_FeederCount;
+ cgDC.feederItemImage = &CG_FeederItemImage;
+ cgDC.feederItemText = &CG_FeederItemText;
+ cgDC.feederSelection = &CG_FeederSelection;
+ //cgDC.setBinding = &trap_Key_SetBinding;
+ //cgDC.getBindingBuf = &trap_Key_GetBindingBuf;
+ //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf;
+ //cgDC.executeText = &trap_Cmd_ExecuteText;
+ cgDC.Error = &Com_Error;
+ cgDC.Print = &Com_Printf;
+ cgDC.ownerDrawWidth = &CG_OwnerDrawWidth;
+ //cgDC.Pause = &CG_Pause;
+ cgDC.registerSound = &trap_S_RegisterSound;
+ cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack;
+ cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack;
+ cgDC.playCinematic = &CG_PlayCinematic;
+ cgDC.stopCinematic = &CG_StopCinematic;
+ cgDC.drawCinematic = &CG_DrawCinematic;
+ cgDC.runCinematicFrame = &CG_RunCinematicFrame;
+
+ Init_Display( &cgDC );
+
+ Menu_Reset( );
+
+ trap_Cvar_VariableStringBuffer( "cg_hudFiles", buff, sizeof( buff ) );
+ hudSet = buff;
+
+ if( hudSet[ 0 ] == '\0' )
+ hudSet = "ui/hud.txt";
+
+ CG_LoadMenus( hudSet );
+}
+
+void CG_AssetCache( void )
+{
+ cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR );
+ cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR );
+ cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN );
+ cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP );
+ cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT );
+ cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT );
+ cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB );
+ cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR );
+ cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB );
+}
+
+/*
+=================
+CG_Init
+
+Called after every level change or subsystem restart
+Will perform callbacks to make the loading info screen update.
+=================
+*/
+void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum )
+{
+ const char *s;
+
+ // clear everything
+ memset( &cgs, 0, sizeof( cgs ) );
+ memset( &cg, 0, sizeof( cg ) );
+ memset( &cg.pmext, 0, sizeof( cg.pmext ) );
+ memset( cg_entities, 0, sizeof( cg_entities ) );
+
+ cg.clientNum = clientNum;
+
+ cgs.processedSnapshotNum = serverMessageNum;
+ cgs.serverCommandSequence = serverCommandSequence;
+
+ // load a few needed things before we do any screen updates
+ cgs.media.whiteShader = trap_R_RegisterShader( "white" );
+ cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" );
+ cgs.media.outlineShader = trap_R_RegisterShader( "outline" );
+
+ //inform UI to repress cursor whilst loading
+ trap_Cvar_Set( "ui_loading", "1" );
+
+ //TA: load overrides
+ BG_InitClassOverrides( );
+ BG_InitBuildableOverrides( );
+ BG_InitAllowedGameElements( );
+
+ //TA: dyn memory
+ CG_InitMemory( );
+
+ CG_RegisterCvars( );
+
+ CG_InitConsoleCommands( );
+
+ //TA: moved up for LoadHudMenu
+ String_Init( );
+
+ //TA: TA UI
+ CG_AssetCache( );
+ CG_LoadHudMenu( ); // load new hud stuff
+
+ cg.weaponSelect = WP_NONE;
+
+ // old servers
+
+ // get the rendering configuration from the client system
+ trap_GetGlconfig( &cgs.glconfig );
+ cgs.screenXScale = cgs.glconfig.vidWidth / 640.0;
+ cgs.screenYScale = cgs.glconfig.vidHeight / 480.0;
+
+ // get the gamestate from the client system
+ trap_GetGameState( &cgs.gameState );
+
+ // check version
+ s = CG_ConfigString( CS_GAME_VERSION );
+
+ if( strcmp( s, GAME_VERSION ) )
+ CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s );
+
+ s = CG_ConfigString( CS_LEVEL_START_TIME );
+ cgs.levelStartTime = atoi( s );
+
+ CG_ParseServerinfo( );
+
+ // load the new map
+ trap_CM_LoadMap( cgs.mapname );
+
+ cg.loading = qtrue; // force players to load instead of defer
+
+ CG_LoadTrailSystems( );
+ CG_UpdateMediaFraction( 0.05f );
+
+ CG_LoadParticleSystems( );
+ CG_UpdateMediaFraction( 0.05f );
+
+ CG_RegisterSounds( );
+ CG_UpdateMediaFraction( 0.60f );
+
+ CG_RegisterGraphics( );
+ CG_UpdateMediaFraction( 0.90f );
+
+ CG_InitWeapons( );
+ CG_UpdateMediaFraction( 0.95f );
+
+ CG_InitUpgrades( );
+ CG_UpdateMediaFraction( 1.0f );
+
+ //TA:
+ CG_InitBuildables( );
+
+ CG_RegisterClients( ); // if low on memory, some clients will be deferred
+
+ cg.loading = qfalse; // future players will be deferred
+
+ CG_InitMarkPolys( );
+
+ // remove the last loading update
+ cg.infoScreenText[ 0 ] = 0;
+
+ // Make sure we have update values (scores)
+ CG_SetConfigValues( );
+
+ CG_StartMusic( );
+
+ CG_ShaderStateChanged( );
+
+ trap_S_ClearLoopingSounds( qtrue );
+
+ trap_Cvar_Set( "ui_loading", "0" );
+}
+
+/*
+=================
+CG_Shutdown
+
+Called before every level change or subsystem restart
+=================
+*/
+void CG_Shutdown( void )
+{
+ // some mods may need to do cleanup work here,
+ // like closing files or archiving session data
+}
+
+/*
+================
+CG_VoIPString
+================
+*/
+char *CG_VoIPString( void )
+{
+ // a generous overestimate of the space needed for 0,1,2...61,62,63
+ static char voipString[ MAX_CLIENTS * 4 ];
+ char voipSendTarget[ MAX_CVAR_VALUE_STRING ];
+
+ trap_Cvar_VariableStringBuffer( "cl_voipSendTarget", voipSendTarget,
+ sizeof( voipSendTarget ) );
+
+ if( Q_stricmp( voipSendTarget, "team" ) == 0 )
+ {
+ int i, slen;
+ for( slen = i = 0; i < cgs.maxclients; i++ )
+ {
+ if( !cgs.clientinfo[ i ].infoValid || i == cg.clientNum )
+ continue;
+ if( cgs.clientinfo[ i ].team != cgs.clientinfo[ cg.clientNum ].team )
+ continue;
+
+ Com_sprintf( &voipString[ slen ], sizeof( voipString ) - slen,
+ "%s%d", ( slen > 0 ) ? "," : "", i );
+ slen = strlen( voipString );
+ if( slen + 1 >= sizeof( voipString ) )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: voipString overflowed\n" );
+ break;
+ }
+ }
+
+ // Notice that if the snprintf was truncated, slen was not updated
+ // so this will remove any trailing commas or partially-completed numbers
+ voipString[ slen ] = '\0';
+ }
+ else if( Q_stricmp( voipSendTarget, "crosshair" ) == 0 )
+ Com_sprintf( voipString, sizeof( voipString ), "%d",
+ CG_CrosshairPlayer( ) );
+ else if( Q_stricmp( voipSendTarget, "attacker" ) == 0 )
+ Com_sprintf( voipString, sizeof( voipString ), "%d",
+ CG_LastAttacker( ) );
+ else
+ return NULL;
+
+ return voipString;
+}
diff --git a/src/cgame/cg_marks.c b/src/cgame/cg_marks.c
new file mode 100644
index 0000000..380f1f0
--- /dev/null
+++ b/src/cgame/cg_marks.c
@@ -0,0 +1,289 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_marks.c -- wall marks
+
+
+#include "cg_local.h"
+
+/*
+===================================================================
+
+MARK POLYS
+
+===================================================================
+*/
+
+
+markPoly_t cg_activeMarkPolys; // double linked list
+markPoly_t *cg_freeMarkPolys; // single linked list
+markPoly_t cg_markPolys[ MAX_MARK_POLYS ];
+static int markTotal;
+
+/*
+===================
+CG_InitMarkPolys
+
+This is called at startup and for tournement restarts
+===================
+*/
+void CG_InitMarkPolys( void )
+{
+ int i;
+
+ memset( cg_markPolys, 0, sizeof( cg_markPolys ) );
+
+ cg_activeMarkPolys.nextMark = &cg_activeMarkPolys;
+ cg_activeMarkPolys.prevMark = &cg_activeMarkPolys;
+ cg_freeMarkPolys = cg_markPolys;
+
+ for( i = 0; i < MAX_MARK_POLYS - 1; i++ )
+ cg_markPolys[ i ].nextMark = &cg_markPolys[ i + 1 ];
+}
+
+
+/*
+==================
+CG_FreeMarkPoly
+==================
+*/
+void CG_FreeMarkPoly( markPoly_t *le )
+{
+ if( !le->prevMark )
+ CG_Error( "CG_FreeLocalEntity: not active" );
+
+ // remove from the doubly linked active list
+ le->prevMark->nextMark = le->nextMark;
+ le->nextMark->prevMark = le->prevMark;
+
+ // the free list is only singly linked
+ le->nextMark = cg_freeMarkPolys;
+ cg_freeMarkPolys = le;
+}
+
+/*
+===================
+CG_AllocMark
+
+Will allways succeed, even if it requires freeing an old active mark
+===================
+*/
+markPoly_t *CG_AllocMark( void )
+{
+ markPoly_t *le;
+ int time;
+
+ if( !cg_freeMarkPolys )
+ {
+ // no free entities, so free the one at the end of the chain
+ // remove the oldest active entity
+ time = cg_activeMarkPolys.prevMark->time;
+
+ while( cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time )
+ CG_FreeMarkPoly( cg_activeMarkPolys.prevMark );
+ }
+
+ le = cg_freeMarkPolys;
+ cg_freeMarkPolys = cg_freeMarkPolys->nextMark;
+
+ memset( le, 0, sizeof( *le ) );
+
+ // link into the active list
+ le->nextMark = cg_activeMarkPolys.nextMark;
+ le->prevMark = &cg_activeMarkPolys;
+ cg_activeMarkPolys.nextMark->prevMark = le;
+ cg_activeMarkPolys.nextMark = le;
+ return le;
+}
+
+
+
+/*
+=================
+CG_ImpactMark
+
+origin should be a point within a unit of the plane
+dir should be the plane normal
+
+temporary marks will not be stored or randomly oriented, but immediately
+passed to the renderer.
+=================
+*/
+#define MAX_MARK_FRAGMENTS 128
+#define MAX_MARK_POINTS 384
+
+void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir,
+ float orientation, float red, float green, float blue, float alpha,
+ qboolean alphaFade, float radius, qboolean temporary )
+{
+ vec3_t axis[ 3 ];
+ float texCoordScale;
+ vec3_t originalPoints[ 4 ];
+ byte colors[ 4 ];
+ int i, j;
+ int numFragments;
+ markFragment_t markFragments[ MAX_MARK_FRAGMENTS ], *mf;
+ vec3_t markPoints[ MAX_MARK_POINTS ];
+ vec3_t projection;
+
+ if( !cg_addMarks.integer )
+ return;
+
+ if( radius <= 0 )
+ CG_Error( "CG_ImpactMark called with <= 0 radius" );
+
+ //if ( markTotal >= MAX_MARK_POLYS ) {
+ // return;
+ //}
+
+ // create the texture axis
+ VectorNormalize2( dir, axis[ 0 ] );
+ PerpendicularVector( axis[ 1 ], axis[ 0 ] );
+ RotatePointAroundVector( axis[ 2 ], axis[ 0 ], axis[ 1 ], orientation );
+ CrossProduct( axis[ 0 ], axis[ 2 ], axis[ 1 ] );
+
+ texCoordScale = 0.5 * 1.0 / radius;
+
+ // create the full polygon
+ for( i = 0; i < 3; i++ )
+ {
+ originalPoints[ 0 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ];
+ originalPoints[ 1 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ];
+ originalPoints[ 2 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ];
+ originalPoints[ 3 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ];
+ }
+
+ // get the fragments
+ VectorScale( dir, -20, projection );
+ numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints,
+ projection, MAX_MARK_POINTS, markPoints[ 0 ],
+ MAX_MARK_FRAGMENTS, markFragments );
+
+ colors[ 0 ] = red * 255;
+ colors[ 1 ] = green * 255;
+ colors[ 2 ] = blue * 255;
+ colors[ 3 ] = alpha * 255;
+
+ for( i = 0, mf = markFragments; i < numFragments; i++, mf++ )
+ {
+ polyVert_t *v;
+ polyVert_t verts[ MAX_VERTS_ON_POLY ];
+ markPoly_t *mark;
+
+ // we have an upper limit on the complexity of polygons
+ // that we store persistantly
+ if( mf->numPoints > MAX_VERTS_ON_POLY )
+ mf->numPoints = MAX_VERTS_ON_POLY;
+
+ for( j = 0, v = verts; j < mf->numPoints; j++, v++ )
+ {
+ vec3_t delta;
+
+ VectorCopy( markPoints[ mf->firstPoint + j ], v->xyz );
+
+ VectorSubtract( v->xyz, origin, delta );
+ v->st[ 0 ] = 0.5 + DotProduct( delta, axis[ 1 ] ) * texCoordScale;
+ v->st[ 1 ] = 0.5 + DotProduct( delta, axis[ 2 ] ) * texCoordScale;
+ *(int *)v->modulate = *(int *)colors;
+ }
+
+ // if it is a temporary (shadow) mark, add it immediately and forget about it
+ if( temporary )
+ {
+ trap_R_AddPolyToScene( markShader, mf->numPoints, verts );
+ continue;
+ }
+
+ // otherwise save it persistantly
+ mark = CG_AllocMark( );
+ mark->time = cg.time;
+ mark->alphaFade = alphaFade;
+ mark->markShader = markShader;
+ mark->poly.numVerts = mf->numPoints;
+ mark->color[ 0 ] = red;
+ mark->color[ 1 ] = green;
+ mark->color[ 2 ] = blue;
+ mark->color[ 3 ] = alpha;
+ memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[ 0 ] ) );
+ markTotal++;
+ }
+}
+
+
+/*
+===============
+CG_AddMarks
+===============
+*/
+#define MARK_TOTAL_TIME 10000
+#define MARK_FADE_TIME 1000
+
+void CG_AddMarks( void )
+{
+ int j;
+ markPoly_t *mp, *next;
+ int t;
+ int fade;
+
+ if( !cg_addMarks.integer )
+ return;
+
+ mp = cg_activeMarkPolys.nextMark;
+ for ( ; mp != &cg_activeMarkPolys; mp = next )
+ {
+ // grab next now, so if the local entity is freed we
+ // still have it
+ next = mp->nextMark;
+
+ // see if it is time to completely remove it
+ if( cg.time > mp->time + MARK_TOTAL_TIME )
+ {
+ CG_FreeMarkPoly( mp );
+ continue;
+ }
+
+ // fade all marks out with time
+ t = mp->time + MARK_TOTAL_TIME - cg.time;
+ if( t < MARK_FADE_TIME )
+ {
+ fade = 255 * t / MARK_FADE_TIME;
+ if( mp->alphaFade )
+ {
+ for( j = 0; j < mp->poly.numVerts; j++ )
+ mp->verts[ j ].modulate[ 3 ] = fade;
+ }
+ else
+ {
+ for( j = 0; j < mp->poly.numVerts; j++ )
+ {
+ mp->verts[ j ].modulate[ 0 ] = mp->color[ 0 ] * fade;
+ mp->verts[ j ].modulate[ 1 ] = mp->color[ 1 ] * fade;
+ mp->verts[ j ].modulate[ 2 ] = mp->color[ 2 ] * fade;
+ }
+ }
+ }
+
+ trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts );
+ }
+}
+
diff --git a/src/cgame/cg_mem.c b/src/cgame/cg_mem.c
new file mode 100644
index 0000000..6cf5ddd
--- /dev/null
+++ b/src/cgame/cg_mem.c
@@ -0,0 +1,202 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cg_local.h"
+
+#define POOLSIZE (256 * 1024)
+#define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value
+#define ROUNDBITS 31 // Round to 32 bytes
+
+struct freememnode
+{
+ // Size of ROUNDBITS
+ int cookie, size; // Size includes node (obviously)
+ struct freememnode *prev, *next;
+};
+
+static char memoryPool[ POOLSIZE ];
+static struct freememnode *freehead;
+static int freemem;
+
+void *CG_Alloc( int size )
+{
+ // Find a free block and allocate.
+ // Does two passes, attempts to fill same-sized free slot first.
+
+ struct freememnode *fmn, *prev, *next, *smallest;
+ int allocsize, smallestsize;
+ char *endptr;
+ int *ptr;
+
+ allocsize = ( size + sizeof(int) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary
+ ptr = NULL;
+
+ smallest = NULL;
+ smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :)
+ for( fmn = freehead; fmn; fmn = fmn->next )
+ {
+ if( fmn->cookie != FREEMEMCOOKIE )
+ CG_Error( "CG_Alloc: Memory corruption detected!\n" );
+
+ if( fmn->size >= allocsize )
+ {
+ // We've got a block
+ if( fmn->size == allocsize )
+ {
+ // Same size, just remove
+
+ prev = fmn->prev;
+ next = fmn->next;
+ if( prev )
+ prev->next = next; // Point previous node to next
+ if( next )
+ next->prev = prev; // Point next node to previous
+ if( fmn == freehead )
+ freehead = next; // Set head pointer to next
+ ptr = (int *) fmn;
+ break; // Stop the loop, this is fine
+ }
+ else
+ {
+ // Keep track of the smallest free slot
+ if( fmn->size < smallestsize )
+ {
+ smallest = fmn;
+ smallestsize = fmn->size;
+ }
+ }
+ }
+ }
+
+ if( !ptr && smallest )
+ {
+ // We found a slot big enough
+ smallest->size -= allocsize;
+ endptr = (char *) smallest + smallest->size;
+ ptr = (int *) endptr;
+ }
+
+ if( ptr )
+ {
+ freemem -= allocsize;
+ if( cg_debugAlloc.integer )
+ CG_Printf( "CG_Alloc of %i bytes (%i left)\n", allocsize, freemem );
+ memset( ptr, 0, allocsize );
+ *ptr++ = allocsize; // Store a copy of size for deallocation
+ return( (void *) ptr );
+ }
+
+ CG_Error( "CG_Alloc: failed on allocation of %i bytes\n", size );
+ return( NULL );
+}
+
+void CG_Free( void *ptr )
+{
+ // Release allocated memory, add it to the free list.
+
+ struct freememnode *fmn;
+ char *freeend;
+ int *freeptr;
+
+ freeptr = ptr;
+ freeptr--;
+
+ freemem += *freeptr;
+ if( cg_debugAlloc.integer )
+ CG_Printf( "CG_Free of %i bytes (%i left)\n", *freeptr, freemem );
+
+ for( fmn = freehead; fmn; fmn = fmn->next )
+ {
+ freeend = ((char *) fmn) + fmn->size;
+ if( freeend == (char *) freeptr )
+ {
+ // Released block can be merged to an existing node
+
+ fmn->size += *freeptr; // Add size of node.
+ return;
+ }
+ }
+ // No merging, add to head of list
+
+ fmn = (struct freememnode *) freeptr;
+ fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr
+ fmn->cookie = FREEMEMCOOKIE;
+ fmn->prev = NULL;
+ fmn->next = freehead;
+ freehead->prev = fmn;
+ freehead = fmn;
+}
+
+void CG_InitMemory( void )
+{
+ // Set up the initial node
+
+ freehead = (struct freememnode *) memoryPool;
+ freehead->cookie = FREEMEMCOOKIE;
+ freehead->size = POOLSIZE;
+ freehead->next = NULL;
+ freehead->prev = NULL;
+ freemem = sizeof(memoryPool);
+}
+
+void CG_DefragmentMemory( void )
+{
+ // If there's a frenzy of deallocation and we want to
+ // allocate something big, this is useful. Otherwise...
+ // not much use.
+
+ struct freememnode *startfmn, *endfmn, *fmn;
+
+ for( startfmn = freehead; startfmn; )
+ {
+ endfmn = (struct freememnode *)(((char *) startfmn) + startfmn->size);
+ for( fmn = freehead; fmn; )
+ {
+ if( fmn->cookie != FREEMEMCOOKIE )
+ CG_Error( "CG_DefragmentMemory: Memory corruption detected!\n" );
+
+ if( fmn == endfmn )
+ {
+ // We can add fmn onto startfmn.
+
+ if( fmn->prev )
+ fmn->prev->next = fmn->next;
+ if( fmn->next )
+ {
+ if( !(fmn->next->prev = fmn->prev) )
+ freehead = fmn->next; // We're removing the head node
+ }
+ startfmn->size += fmn->size;
+ memset( fmn, 0, sizeof(struct freememnode) ); // A redundant call, really.
+
+ startfmn = freehead;
+ endfmn = fmn = NULL; // Break out of current loop
+ }
+ else
+ fmn = fmn->next;
+ }
+
+ if( endfmn )
+ startfmn = startfmn->next; // endfmn acts as a 'restart' flag here
+ }
+}
diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c
new file mode 100644
index 0000000..80c4b23
--- /dev/null
+++ b/src/cgame/cg_particles.c
@@ -0,0 +1,2567 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_particles.c -- the particle system
+
+
+#include "cg_local.h"
+
+static baseParticleSystem_t baseParticleSystems[ MAX_BASEPARTICLE_SYSTEMS ];
+static baseParticleEjector_t baseParticleEjectors[ MAX_BASEPARTICLE_EJECTORS ];
+static baseParticle_t baseParticles[ MAX_BASEPARTICLES ];
+static int numBaseParticleSystems = 0;
+static int numBaseParticleEjectors = 0;
+static int numBaseParticles = 0;
+
+static particleSystem_t particleSystems[ MAX_PARTICLE_SYSTEMS ];
+static particleEjector_t particleEjectors[ MAX_PARTICLE_EJECTORS ];
+static particle_t particles[ MAX_PARTICLES ];
+static particle_t *sortedParticles[ MAX_PARTICLES ];
+static particle_t *radixBuffer[ MAX_PARTICLES ];
+
+/*
+===============
+CG_LerpValues
+
+Lerp between two values
+===============
+*/
+static float CG_LerpValues( float a, float b, float f )
+{
+ if( b == PARTICLES_SAME_AS_INITIAL )
+ return a;
+ else
+ return ( (a) + (f) * ( (b) - (a) ) );
+}
+
+/*
+===============
+CG_RandomiseValue
+
+Randomise some value by some variance
+===============
+*/
+static float CG_RandomiseValue( float value, float variance )
+{
+ if( value != 0.0f )
+ return value * ( 1.0f + ( random( ) * variance ) );
+ else
+ return random( ) * variance;
+}
+
+/*
+===============
+CG_SpreadVector
+
+Randomly spread a vector by some amount
+===============
+*/
+static void CG_SpreadVector( vec3_t v, float spread )
+{
+ vec3_t p, r1, r2;
+ float randomSpread = crandom( ) * spread;
+ float randomRotation = random( ) * 360.0f;
+
+ PerpendicularVector( p, v );
+
+ RotatePointAroundVector( r1, p, v, randomSpread );
+ RotatePointAroundVector( r2, v, r1, randomRotation );
+
+ VectorCopy( r2, v );
+}
+
+/*
+===============
+CG_DestroyParticle
+
+Destroy an individual particle
+===============
+*/
+static void CG_DestroyParticle( particle_t *p, vec3_t impactNormal )
+{
+ //this particle has an onDeath particle system attached
+ if( p->class->onDeathSystemName[ 0 ] != '\0' )
+ {
+ particleSystem_t *ps;
+
+ ps = CG_SpawnNewParticleSystem( p->class->onDeathSystemHandle );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ if( impactNormal )
+ CG_SetParticleSystemNormal( ps, impactNormal );
+
+ CG_SetAttachmentPoint( &ps->attachment, p->origin );
+ CG_AttachToPoint( &ps->attachment );
+ }
+ }
+
+ p->valid = qfalse;
+
+ //this gives other systems a couple of
+ //frames to realise the particle is gone
+ p->frameWhenInvalidated = cg.clientFrame;
+}
+
+/*
+===============
+CG_SpawnNewParticle
+
+Introduce a new particle into the world
+===============
+*/
+static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *parent )
+{
+ int i, j;
+ particle_t *p = NULL;
+ particleEjector_t *pe = parent;
+ particleSystem_t *ps = parent->parent;
+ vec3_t attachmentPoint, attachmentVelocity;
+ vec3_t transform[ 3 ];
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ {
+ p = &particles[ i ];
+
+ //FIXME: the + 1 may be unnecessary
+ if( !p->valid && cg.clientFrame > p->frameWhenInvalidated + 1 )
+ {
+ memset( p, 0, sizeof( particle_t ) );
+
+ //found a free slot
+ p->class = bp;
+ p->parent = pe;
+
+ p->birthTime = cg.time;
+ p->lifeTime = (int)CG_RandomiseValue( (float)bp->lifeTime, bp->lifeTimeRandFrac );
+
+ p->radius.delay = (int)CG_RandomiseValue( (float)bp->radius.delay, bp->radius.delayRandFrac );
+ p->radius.initial = CG_RandomiseValue( bp->radius.initial, bp->radius.initialRandFrac );
+ p->radius.final = CG_RandomiseValue( bp->radius.final, bp->radius.finalRandFrac );
+
+ p->alpha.delay = (int)CG_RandomiseValue( (float)bp->alpha.delay, bp->alpha.delayRandFrac );
+ p->alpha.initial = CG_RandomiseValue( bp->alpha.initial, bp->alpha.initialRandFrac );
+ p->alpha.final = CG_RandomiseValue( bp->alpha.final, bp->alpha.finalRandFrac );
+
+ p->rotation.delay = (int)CG_RandomiseValue( (float)bp->rotation.delay, bp->rotation.delayRandFrac );
+ p->rotation.initial = CG_RandomiseValue( bp->rotation.initial, bp->rotation.initialRandFrac );
+ p->rotation.final = CG_RandomiseValue( bp->rotation.final, bp->rotation.finalRandFrac );
+
+ p->dLightRadius.delay =
+ (int)CG_RandomiseValue( (float)bp->dLightRadius.delay, bp->dLightRadius.delayRandFrac );
+ p->dLightRadius.initial =
+ CG_RandomiseValue( bp->dLightRadius.initial, bp->dLightRadius.initialRandFrac );
+ p->dLightRadius.final =
+ CG_RandomiseValue( bp->dLightRadius.final, bp->dLightRadius.finalRandFrac );
+
+ p->colorDelay = CG_RandomiseValue( bp->colorDelay, bp->colorDelayRandFrac );
+
+ p->bounceMarkRadius = CG_RandomiseValue( bp->bounceMarkRadius, bp->bounceMarkRadiusRandFrac );
+ p->bounceMarkCount =
+ rint( CG_RandomiseValue( (float)bp->bounceMarkCount, bp->bounceMarkCountRandFrac ) );
+ p->bounceSoundCount =
+ rint( CG_RandomiseValue( (float)bp->bounceSoundCount, bp->bounceSoundCountRandFrac ) );
+
+ if( bp->numModels )
+ {
+ p->model = bp->models[ rand( ) % bp->numModels ];
+
+ if( bp->modelAnimation.frameLerp < 0 )
+ {
+ bp->modelAnimation.frameLerp = p->lifeTime / bp->modelAnimation.numFrames;
+ bp->modelAnimation.initialLerp = p->lifeTime / bp->modelAnimation.numFrames;
+ }
+ }
+
+ if( !CG_AttachmentPoint( &ps->attachment, attachmentPoint ) )
+ return NULL;
+
+ VectorCopy( attachmentPoint, p->origin );
+
+ if( CG_AttachmentAxis( &ps->attachment, transform ) )
+ {
+ vec3_t transDisplacement;
+
+ VectorMatrixMultiply( bp->displacement, transform, transDisplacement );
+ VectorAdd( p->origin, transDisplacement, p->origin );
+ }
+ else
+ VectorAdd( p->origin, bp->displacement, p->origin );
+
+ for( j = 0; j <= 2; j++ )
+ p->origin[ j ] += ( crandom( ) * bp->randDisplacement );
+
+ switch( bp->velMoveType )
+ {
+ case PMT_STATIC:
+ if( bp->velMoveValues.dirType == PMD_POINT )
+ VectorSubtract( bp->velMoveValues.point, p->origin, p->velocity );
+ else if( bp->velMoveValues.dirType == PMD_LINEAR )
+ VectorCopy( bp->velMoveValues.dir, p->velocity );
+ break;
+
+ case PMT_STATIC_TRANSFORM:
+ if( !CG_AttachmentAxis( &ps->attachment, transform ) )
+ return NULL;
+
+ if( bp->velMoveValues.dirType == PMD_POINT )
+ {
+ vec3_t transPoint;
+
+ VectorMatrixMultiply( bp->velMoveValues.point, transform, transPoint );
+ VectorSubtract( transPoint, p->origin, p->velocity );
+ }
+ else if( bp->velMoveValues.dirType == PMD_LINEAR )
+ VectorMatrixMultiply( bp->velMoveValues.dir, transform, p->velocity );
+ break;
+
+ case PMT_TAG:
+ case PMT_CENT_ANGLES:
+ if( bp->velMoveValues.dirType == PMD_POINT )
+ VectorSubtract( attachmentPoint, p->origin, p->velocity );
+ else if( bp->velMoveValues.dirType == PMD_LINEAR )
+ {
+ if( !CG_AttachmentDir( &ps->attachment, p->velocity ) )
+ return NULL;
+ }
+ break;
+
+ case PMT_NORMAL:
+ if( !ps->normalValid )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a particle with velocityType "
+ "normal has no normal\n" );
+ return NULL;
+ }
+
+ VectorCopy( ps->normal, p->velocity );
+
+ //normal displacement
+ VectorNormalize( p->velocity );
+ VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin );
+ break;
+ }
+
+ VectorNormalize( p->velocity );
+ CG_SpreadVector( p->velocity, bp->velMoveValues.dirRandAngle );
+ VectorScale( p->velocity,
+ CG_RandomiseValue( bp->velMoveValues.mag, bp->velMoveValues.magRandFrac ),
+ p->velocity );
+
+ if( CG_AttachmentVelocity( &ps->attachment, attachmentVelocity ) )
+ {
+ VectorMA( p->velocity,
+ CG_RandomiseValue( bp->velMoveValues.parentVelFrac,
+ bp->velMoveValues.parentVelFracRandFrac ), attachmentVelocity, p->velocity );
+ }
+
+ p->lastEvalTime = cg.time;
+
+ p->valid = qtrue;
+
+ //this particle has a child particle system attached
+ if( bp->childSystemName[ 0 ] != '\0' )
+ {
+ particleSystem_t *ps = CG_SpawnNewParticleSystem( bp->childSystemHandle );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentParticle( &ps->attachment, p );
+ CG_AttachToParticle( &ps->attachment );
+ }
+ }
+
+ //this particle has a child trail system attached
+ if( bp->childTrailSystemName[ 0 ] != '\0' )
+ {
+ trailSystem_t *ts = CG_SpawnNewTrailSystem( bp->childTrailSystemHandle );
+
+ if( CG_IsTrailSystemValid( &ts ) )
+ {
+ CG_SetAttachmentParticle( &ts->frontAttachment, p );
+ CG_AttachToParticle( &ts->frontAttachment );
+ }
+ }
+
+ break;
+ }
+ }
+
+ return p;
+}
+
+
+/*
+===============
+CG_SpawnNewParticles
+
+Check if there are any ejectors that should be
+introducing new particles
+===============
+*/
+static void CG_SpawnNewParticles( void )
+{
+ int i, j;
+ particle_t *p;
+ particleSystem_t *ps;
+ particleEjector_t *pe;
+ baseParticleEjector_t *bpe;
+ float lerpFrac;
+ int count;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+ ps = pe->parent;
+
+ if( pe->valid )
+ {
+ //a non attached particle system can't make particles
+ if( !CG_Attached( &ps->attachment ) )
+ continue;
+
+ bpe = particleEjectors[ i ].class;
+
+ //if this system is scheduled for removal don't make any new particles
+ if( !ps->lazyRemove )
+ {
+ while( pe->nextEjectionTime <= cg.time &&
+ ( pe->count > 0 || pe->totalParticles == PARTICLES_INFINITE ) )
+ {
+ for( j = 0; j < bpe->numParticles; j++ )
+ CG_SpawnNewParticle( bpe->particles[ j ], pe );
+
+ if( pe->count > 0 )
+ pe->count--;
+
+ //calculate next ejection time
+ lerpFrac = 1.0 - ( (float)pe->count / (float)pe->totalParticles );
+ pe->nextEjectionTime = cg.time + (int)CG_RandomiseValue(
+ CG_LerpValues( pe->ejectPeriod.initial,
+ pe->ejectPeriod.final,
+ lerpFrac ),
+ pe->ejectPeriod.randFrac );
+ }
+ }
+
+ if( pe->count == 0 || ps->lazyRemove )
+ {
+ count = 0;
+
+ //wait for child particles to die before declaring this pe invalid
+ for( j = 0; j < MAX_PARTICLES; j++ )
+ {
+ p = &particles[ j ];
+
+ if( p->valid && p->parent == pe )
+ count++;
+ }
+
+ if( !count )
+ pe->valid = qfalse;
+ }
+ }
+ }
+}
+
+
+/*
+===============
+CG_SpawnNewParticleEjector
+
+Allocate a new particle ejector
+===============
+*/
+static particleEjector_t *CG_SpawnNewParticleEjector( baseParticleEjector_t *bpe,
+ particleSystem_t *parent )
+{
+ int i;
+ particleEjector_t *pe = NULL;
+ particleSystem_t *ps = parent;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+
+ if( !pe->valid )
+ {
+ memset( pe, 0, sizeof( particleEjector_t ) );
+
+ //found a free slot
+ pe->class = bpe;
+ pe->parent = ps;
+
+ pe->ejectPeriod.initial = bpe->eject.initial;
+ pe->ejectPeriod.final = bpe->eject.final;
+ pe->ejectPeriod.randFrac = bpe->eject.randFrac;
+
+ pe->nextEjectionTime = cg.time +
+ (int)CG_RandomiseValue( (float)bpe->eject.delay, bpe->eject.delayRandFrac );
+ pe->count = pe->totalParticles =
+ (int)rint( CG_RandomiseValue( (float)bpe->totalParticles, bpe->totalParticlesRandFrac ) );
+
+ pe->valid = qtrue;
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "PE %s created\n", ps->class->name );
+
+ break;
+ }
+ }
+
+ return pe;
+}
+
+
+/*
+===============
+CG_SpawnNewParticleSystem
+
+Allocate a new particle system
+===============
+*/
+particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle )
+{
+ int i, j;
+ particleSystem_t *ps = NULL;
+ baseParticleSystem_t *bps = &baseParticleSystems[ psHandle - 1 ];
+
+ if( !bps->registered )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a particle system has not been registered yet\n" );
+ return NULL;
+ }
+
+ for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ )
+ {
+ ps = &particleSystems[ i ];
+
+ if( !ps->valid )
+ {
+ memset( ps, 0, sizeof( particleSystem_t ) );
+
+ //found a free slot
+ ps->class = bps;
+
+ ps->valid = qtrue;
+ ps->lazyRemove = qfalse;
+
+ for( j = 0; j < bps->numEjectors; j++ )
+ CG_SpawnNewParticleEjector( bps->ejectors[ j ], ps );
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "PS %s created\n", bps->name );
+
+ break;
+ }
+ }
+
+ return ps;
+}
+
+/*
+===============
+CG_RegisterParticleSystem
+
+Load the shaders required for a particle system
+===============
+*/
+qhandle_t CG_RegisterParticleSystem( char *name )
+{
+ int i, j, k, l;
+ baseParticleSystem_t *bps;
+ baseParticleEjector_t *bpe;
+ baseParticle_t *bp;
+
+ for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ )
+ {
+ bps = &baseParticleSystems[ i ];
+
+ if( !Q_stricmpn( bps->name, name, MAX_QPATH ) )
+ {
+ //already registered
+ if( bps->registered )
+ return i + 1;
+
+ for( j = 0; j < bps->numEjectors; j++ )
+ {
+ bpe = bps->ejectors[ j ];
+
+ for( l = 0; l < bpe->numParticles; l++ )
+ {
+ bp = bpe->particles[ l ];
+
+ for( k = 0; k < bp->numFrames; k++ )
+ bp->shaders[ k ] = trap_R_RegisterShader( bp->shaderNames[ k ] );
+
+ for( k = 0; k < bp->numModels; k++ )
+ bp->models[ k ] = trap_R_RegisterModel( bp->modelNames[ k ] );
+
+ if( bp->bounceMarkName[ 0 ] != '\0' )
+ bp->bounceMark = trap_R_RegisterShader( bp->bounceMarkName );
+
+ if( bp->bounceSoundName[ 0 ] != '\0' )
+ bp->bounceSound = trap_S_RegisterSound( bp->bounceSoundName, qfalse );
+
+ //recursively register any children
+ if( bp->childSystemName[ 0 ] != '\0' )
+ {
+ //don't care about a handle for children since
+ //the system deals with it
+ CG_RegisterParticleSystem( bp->childSystemName );
+ }
+
+ if( bp->onDeathSystemName[ 0 ] != '\0' )
+ {
+ //don't care about a handle for children since
+ //the system deals with it
+ CG_RegisterParticleSystem( bp->onDeathSystemName );
+ }
+
+ if( bp->childTrailSystemName[ 0 ] != '\0' )
+ bp->childTrailSystemHandle = CG_RegisterTrailSystem( bp->childTrailSystemName );
+ }
+ }
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "Registered particle system %s\n", name );
+
+ bps->registered = qtrue;
+
+ //avoid returning 0
+ return i + 1;
+ }
+ }
+
+ CG_Printf( S_COLOR_RED "ERROR: failed to register particle system %s\n", name );
+ return 0;
+}
+
+
+/*
+===============
+CG_ParseValueAndVariance
+
+Parse a value and its random variance
+===============
+*/
+static void CG_ParseValueAndVariance( char *token, float *value, float *variance, qboolean allowNegative )
+{
+ char valueBuffer[ 16 ];
+ char varianceBuffer[ 16 ];
+ char *variancePtr = NULL, *varEndPointer = NULL;
+ float localValue = 0.0f;
+ float localVariance = 0.0f;
+
+ Q_strncpyz( valueBuffer, token, sizeof( valueBuffer ) );
+ Q_strncpyz( varianceBuffer, token, sizeof( varianceBuffer ) );
+
+ variancePtr = strchr( valueBuffer, '~' );
+
+ //variance included
+ if( variancePtr )
+ {
+ variancePtr[ 0 ] = '\0';
+ variancePtr++;
+
+ localValue = atof_neg( valueBuffer, allowNegative );
+
+ varEndPointer = strchr( variancePtr, '%' );
+
+ if( varEndPointer )
+ {
+ varEndPointer[ 0 ] = '\0';
+ localVariance = atof_neg( variancePtr, qfalse ) / 100.0f;
+ }
+ else
+ {
+ if( localValue != 0.0f )
+ localVariance = atof_neg( variancePtr, qfalse ) / localValue;
+ else
+ localVariance = atof_neg( variancePtr, qfalse );
+ }
+ }
+ else
+ localValue = atof_neg( valueBuffer, allowNegative );
+
+ if( value != NULL )
+ *value = localValue;
+
+ if( variance != NULL )
+ *variance = localVariance;
+}
+
+/*
+===============
+CG_ParseColor
+===============
+*/
+static qboolean CG_ParseColor( byte *c, char **text_p )
+{
+ char *token;
+ int i;
+
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) );
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_ParseParticle
+
+Parse a particle section
+===============
+*/
+static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p )
+{
+ char *token;
+ float number, randFrac;
+ int i;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "bounce" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "cull" ) )
+ {
+ bp->bounceCull = qtrue;
+
+ bp->bounceFrac = -1.0f;
+ bp->bounceFracRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceFrac = number;
+ bp->bounceFracRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "bounceMark" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceMarkCount = number;
+ bp->bounceMarkCountRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceMarkRadius = number;
+ bp->bounceMarkRadiusRandFrac = randFrac;
+
+ token = COM_ParseExt( text_p, qfalse );
+ if( !*token )
+ break;
+
+ Q_strncpyz( bp->bounceMarkName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "bounceSound" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceSoundCount = number;
+ bp->bounceSoundCountRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ Q_strncpyz( bp->bounceSoundName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "shader" ) )
+ {
+ if( bp->numModels > 0 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: 'shader' not allowed in "
+ "conjunction with 'model'\n", token );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "sync" ) )
+ bp->framerate = 0.0f;
+ else
+ bp->framerate = atof_neg( token, qfalse );
+
+ token = COM_ParseExt( text_p, qfalse );
+ if( !*token )
+ break;
+
+ while( *token && bp->numFrames < MAX_PS_SHADER_FRAMES )
+ {
+ Q_strncpyz( bp->shaderNames[ bp->numFrames++ ], token, MAX_QPATH );
+ token = COM_ParseExt( text_p, qfalse );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "model" ) )
+ {
+ if( bp->numFrames > 0 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: 'model' not allowed in "
+ "conjunction with 'shader'\n", token );
+ break;
+ }
+
+ token = COM_ParseExt( text_p, qfalse );
+ if( !*token )
+ break;
+
+ while( *token && bp->numModels < MAX_PS_MODELS )
+ {
+ Q_strncpyz( bp->modelNames[ bp->numModels++ ], token, MAX_QPATH );
+ token = COM_ParseExt( text_p, qfalse );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "modelAnimation" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ bp->modelAnimation.firstFrame = atoi_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ bp->modelAnimation.numFrames = atoi( token );
+ bp->modelAnimation.reversed = qfalse;
+ bp->modelAnimation.flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( bp->modelAnimation.numFrames < 0 )
+ {
+ bp->modelAnimation.numFrames = -bp->modelAnimation.numFrames;
+ bp->modelAnimation.reversed = qtrue;
+ }
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ bp->modelAnimation.loopFrames = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "sync" ) )
+ {
+ bp->modelAnimation.frameLerp = -1;
+ bp->modelAnimation.initialLerp = -1;
+ }
+ else
+ {
+ float fps = atof_neg( token, qfalse );
+
+ if( fps == 0.0f )
+ fps = 1.0f;
+
+ bp->modelAnimation.frameLerp = 1000 / fps;
+ bp->modelAnimation.initialLerp = 1000 / fps;
+ }
+
+ continue;
+ }
+ ///
+ else if( !Q_stricmp( token, "velocityType" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "static" ) )
+ bp->velMoveType = PMT_STATIC;
+ else if( !Q_stricmp( token, "static_transform" ) )
+ bp->velMoveType = PMT_STATIC_TRANSFORM;
+ else if( !Q_stricmp( token, "tag" ) )
+ bp->velMoveType = PMT_TAG;
+ else if( !Q_stricmp( token, "cent" ) )
+ bp->velMoveType = PMT_CENT_ANGLES;
+ else if( !Q_stricmp( token, "normal" ) )
+ bp->velMoveType = PMT_NORMAL;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocityDir" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "linear" ) )
+ bp->velMoveValues.dirType = PMD_LINEAR;
+ else if( !Q_stricmp( token, "point" ) )
+ bp->velMoveValues.dirType = PMD_POINT;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocityMagnitude" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->velMoveValues.mag = number;
+ bp->velMoveValues.magRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "parentVelocityFraction" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->velMoveValues.parentVelFrac = number;
+ bp->velMoveValues.parentVelFracRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocity" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->velMoveValues.dir[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->velMoveValues.dirRandAngle = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocityPoint" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->velMoveValues.point[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->velMoveValues.pointRandAngle = randFrac;
+
+ continue;
+ }
+ ///
+ else if( !Q_stricmp( token, "accelerationType" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "static" ) )
+ bp->accMoveType = PMT_STATIC;
+ else if( !Q_stricmp( token, "static_transform" ) )
+ bp->accMoveType = PMT_STATIC_TRANSFORM;
+ else if( !Q_stricmp( token, "tag" ) )
+ bp->accMoveType = PMT_TAG;
+ else if( !Q_stricmp( token, "cent" ) )
+ bp->accMoveType = PMT_CENT_ANGLES;
+ else if( !Q_stricmp( token, "normal" ) )
+ bp->accMoveType = PMT_NORMAL;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "accelerationDir" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "linear" ) )
+ bp->accMoveValues.dirType = PMD_LINEAR;
+ else if( !Q_stricmp( token, "point" ) )
+ bp->accMoveValues.dirType = PMD_POINT;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "accelerationMagnitude" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->accMoveValues.mag = number;
+ bp->accMoveValues.magRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "acceleration" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->accMoveValues.dir[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->accMoveValues.dirRandAngle = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "accelerationPoint" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->accMoveValues.point[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->accMoveValues.pointRandAngle = randFrac;
+
+ continue;
+ }
+ ///
+ else if( !Q_stricmp( token, "displacement" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->displacement[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->randDisplacement = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "normalDisplacement" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->normalDisplacement = atof_neg( token, qtrue );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "overdrawProtection" ) )
+ {
+ bp->overdrawProtection = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "realLight" ) )
+ {
+ bp->realLight = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "dynamicLight" ) )
+ {
+ bp->dynamicLight = qtrue;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->dLightRadius.delay = (int)number;
+ bp->dLightRadius.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->dLightRadius.initial = number;
+ bp->dLightRadius.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->dLightRadius.final = PARTICLES_SAME_AS_INITIAL;
+ bp->dLightRadius.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->dLightRadius.final = number;
+ bp->dLightRadius.finalRandFrac = randFrac;
+ }
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseColor( bp->dLightColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "cullOnStartSolid" ) )
+ {
+ bp->cullOnStartSolid = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "radius" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->radius.delay = (int)number;
+ bp->radius.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->radius.initial = number;
+ bp->radius.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->radius.final = PARTICLES_SAME_AS_INITIAL;
+ bp->radius.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->radius.final = number;
+ bp->radius.finalRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "alpha" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->alpha.delay = (int)number;
+ bp->alpha.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->alpha.initial = number;
+ bp->alpha.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->alpha.final = PARTICLES_SAME_AS_INITIAL;
+ bp->alpha.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->alpha.final = number;
+ bp->alpha.finalRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "color" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->colorDelay = (int)number;
+ bp->colorDelayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseColor( bp->initialColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->finalColor[ 0 ] = bp->initialColor[ 0 ];
+ bp->finalColor[ 1 ] = bp->initialColor[ 1 ];
+ bp->finalColor[ 2 ] = bp->initialColor[ 2 ];
+ }
+ else if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseColor( bp->finalColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "rotation" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->rotation.delay = (int)number;
+ bp->rotation.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qtrue );
+
+ bp->rotation.initial = number;
+ bp->rotation.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->rotation.final = PARTICLES_SAME_AS_INITIAL;
+ bp->rotation.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qtrue );
+
+ bp->rotation.final = number;
+ bp->rotation.finalRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "lifeTime" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->lifeTime = (int)number;
+ bp->lifeTimeRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "childSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( bp->childSystemName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "onDeathSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( bp->onDeathSystemName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "childTrailSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( bp->childTrailSystemName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this particle
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_InitialiseBaseParticle
+===============
+*/
+static void CG_InitialiseBaseParticle( baseParticle_t *bp )
+{
+ memset( bp, 0, sizeof( baseParticle_t ) );
+
+ memset( bp->initialColor, 0xFF, sizeof( bp->initialColor ) );
+ memset( bp->finalColor, 0xFF, sizeof( bp->finalColor ) );
+}
+
+/*
+===============
+CG_ParseParticleEjector
+
+Parse a particle ejector section
+===============
+*/
+static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text_p )
+{
+ char *token;
+ float number, randFrac;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ CG_InitialiseBaseParticle( &baseParticles[ numBaseParticles ] );
+
+ if( !CG_ParseParticle( &baseParticles[ numBaseParticles ], text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse particle\n" );
+ return qfalse;
+ }
+
+ if( bpe->numParticles == MAX_PARTICLES_PER_EJECTOR )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: ejector has > %d particles\n", MAX_PARTICLES_PER_EJECTOR );
+ return qfalse;
+ }
+ else if( numBaseParticles == MAX_BASEPARTICLES )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of particles (%d) reached\n", MAX_BASEPARTICLES );
+ return qfalse;
+ }
+ else
+ {
+ //start parsing particles again
+ bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ];
+ bpe->numParticles++;
+ numBaseParticles++;
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "delay" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bpe->eject.delay = (int)number;
+ bpe->eject.delayRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "period" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bpe->eject.initial = atoi_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ bpe->eject.final = PARTICLES_SAME_AS_INITIAL;
+ else
+ bpe->eject.final = atoi_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &bpe->eject.randFrac, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "count" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "infinite" ) )
+ {
+ bpe->totalParticles = PARTICLES_INFINITE;
+ bpe->totalParticlesRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bpe->totalParticles = (int)number;
+ bpe->totalParticlesRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "particle" ) ) //acceptable text
+ continue;
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this particle ejector
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle ejector\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+
+/*
+===============
+CG_ParseParticleSystem
+
+Parse a particle system section
+===============
+*/
+static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p, const char *name )
+{
+ char *token;
+ baseParticleEjector_t *bpe;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseParticleEjector( &baseParticleEjectors[ numBaseParticleEjectors ], text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse particle ejector\n" );
+ return qfalse;
+ }
+
+ bpe = &baseParticleEjectors[ numBaseParticleEjectors ];
+
+ //check for infinite count + zero period
+ if( bpe->totalParticles == PARTICLES_INFINITE &&
+ ( bpe->eject.initial == 0.0f || bpe->eject.final == 0.0f ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: ejector with 'count infinite' potentially has zero period\n" );
+ return qfalse;
+ }
+
+ if( bps->numEjectors == MAX_EJECTORS_PER_SYSTEM )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: particle system has > %d ejectors\n", MAX_EJECTORS_PER_SYSTEM );
+ return qfalse;
+ }
+ else if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n",
+ MAX_BASEPARTICLE_EJECTORS );
+ return qfalse;
+ }
+ else
+ {
+ //start parsing ejectors again
+ bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ];
+ bps->numEjectors++;
+ numBaseParticleEjectors++;
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "thirdPersonOnly" ) )
+ bps->thirdPersonOnly = qtrue;
+ else if( !Q_stricmp( token, "ejector" ) ) //acceptable text
+ continue;
+ else if( !Q_stricmp( token, "}" ) )
+ {
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "Parsed particle system %s\n", name );
+
+ return qtrue; //reached the end of this particle system
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle system %s\n", token, bps->name );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_ParseParticleFile
+
+Load the particle systems from a particle file
+===============
+*/
+static qboolean CG_ParseParticleFile( const char *fileName )
+{
+ char *text_p;
+ int i;
+ int len;
+ char *token;
+ char text[ 32000 ];
+ char psName[ MAX_QPATH ];
+ qboolean psNameSet = qfalse;
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: particle file %s too long\n", fileName );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( psNameSet )
+ {
+ //check for name space clashes
+ for( i = 0; i < numBaseParticleSystems; i++ )
+ {
+ if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName );
+ return qfalse;
+ }
+ }
+
+ Q_strncpyz( baseParticleSystems[ numBaseParticleSystems ].name, psName, MAX_QPATH );
+
+ if( !CG_ParseParticleSystem( &baseParticleSystems[ numBaseParticleSystems ], &text_p, psName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse particle system %s\n", fileName, psName );
+ return qfalse;
+ }
+
+ //start parsing particle systems again
+ psNameSet = qfalse;
+
+ if( numBaseParticleSystems == MAX_BASEPARTICLE_SYSTEMS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of particle systems (%d) reached\n",
+ MAX_BASEPARTICLE_SYSTEMS );
+ return qfalse;
+ }
+ else
+ numBaseParticleSystems++;
+
+ continue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unamed particle system\n" );
+ return qfalse;
+ }
+ }
+
+ if( !psNameSet )
+ {
+ Q_strncpyz( psName, token, sizeof( psName ) );
+ psNameSet = qtrue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: particle system already named\n" );
+ return qfalse;
+ }
+ }
+
+ return qtrue;
+}
+
+
+/*
+===============
+CG_LoadParticleSystems
+
+Load particle systems from .particle files
+===============
+*/
+void CG_LoadParticleSystems( void )
+{
+ int i, j, numFiles, fileLen;
+ char fileList[ MAX_PARTICLE_FILES * MAX_QPATH ];
+ char fileName[ MAX_QPATH ];
+ char *filePtr;
+
+ //clear out the old
+ numBaseParticleSystems = 0;
+ numBaseParticleEjectors = 0;
+ numBaseParticles = 0;
+
+ for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ )
+ {
+ baseParticleSystem_t *bps = &baseParticleSystems[ i ];
+ memset( bps, 0, sizeof( baseParticleSystem_t ) );
+ }
+
+ for( i = 0; i < MAX_BASEPARTICLE_EJECTORS; i++ )
+ {
+ baseParticleEjector_t *bpe = &baseParticleEjectors[ i ];
+ memset( bpe, 0, sizeof( baseParticleEjector_t ) );
+ }
+
+ for( i = 0; i < MAX_BASEPARTICLES; i++ )
+ {
+ baseParticle_t *bp = &baseParticles[ i ];
+ memset( bp, 0, sizeof( baseParticle_t ) );
+ }
+
+
+ //and bring in the new
+ numFiles = trap_FS_GetFileList( "scripts", ".particle",
+ fileList, MAX_PARTICLE_FILES * MAX_QPATH );
+ filePtr = fileList;
+
+ for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ strcpy( fileName, "scripts/" );
+ strcat( fileName, filePtr );
+ CG_Printf( "...loading '%s'\n", fileName );
+ CG_ParseParticleFile( fileName );
+ }
+
+ //connect any child systems to their psHandle
+ for( i = 0; i < numBaseParticles; i++ )
+ {
+ baseParticle_t *bp = &baseParticles[ i ];
+
+ if( bp->childSystemName[ 0 ] )
+ {
+ //particle class has a child, resolve the name
+ for( j = 0; j < numBaseParticleSystems; j++ )
+ {
+ baseParticleSystem_t *bps = &baseParticleSystems[ j ];
+
+ if( !Q_stricmp( bps->name, bp->childSystemName ) )
+ {
+ //FIXME: add checks for cycles and infinite children
+
+ bp->childSystemHandle = j + 1;
+
+ break;
+ }
+ }
+
+ if( j == numBaseParticleSystems )
+ {
+ //couldn't find named particle system
+ CG_Printf( S_COLOR_YELLOW "WARNING: failed to find child %s\n", bp->childSystemName );
+ bp->childSystemName[ 0 ] = '\0';
+ }
+ }
+
+ if( bp->onDeathSystemName[ 0 ] )
+ {
+ //particle class has a child, resolve the name
+ for( j = 0; j < numBaseParticleSystems; j++ )
+ {
+ baseParticleSystem_t *bps = &baseParticleSystems[ j ];
+
+ if( !Q_stricmp( bps->name, bp->onDeathSystemName ) )
+ {
+ //FIXME: add checks for cycles and infinite children
+
+ bp->onDeathSystemHandle = j + 1;
+
+ break;
+ }
+ }
+
+ if( j == numBaseParticleSystems )
+ {
+ //couldn't find named particle system
+ CG_Printf( S_COLOR_YELLOW "WARNING: failed to find onDeath system %s\n", bp->onDeathSystemName );
+ bp->onDeathSystemName[ 0 ] = '\0';
+ }
+ }
+ }
+}
+
+/*
+===============
+CG_SetParticleSystemNormal
+===============
+*/
+void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal )
+{
+ if( ps == NULL || !ps->valid )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" );
+ return;
+ }
+
+ ps->normalValid = qtrue;
+ VectorCopy( normal, ps->normal );
+ VectorNormalize( ps->normal );
+}
+
+
+/*
+===============
+CG_DestroyParticleSystem
+
+Destroy a particle system
+
+This doesn't actually invalidate anything, it just stops
+particle ejectors from producing new particles so the
+garbage collector will eventually remove this system.
+However is does set the pointer to NULL so the user is
+unable to manipulate this particle system any longer.
+===============
+*/
+void CG_DestroyParticleSystem( particleSystem_t **ps )
+{
+ int i;
+ particleEjector_t *pe;
+
+ if( *ps == NULL || !(*ps)->valid )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to destroy a NULL particle system\n" );
+ return;
+ }
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "PS destroyed\n" );
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+
+ if( pe->valid && pe->parent == *ps )
+ pe->totalParticles = pe->count = 0;
+ }
+
+ *ps = NULL;
+}
+
+/*
+===============
+CG_IsParticleSystemInfinite
+
+Test a particle system for 'count infinite' ejectors
+===============
+*/
+qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps )
+{
+ int i;
+ particleEjector_t *pe;
+
+ if( ps == NULL )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to test a NULL particle system\n" );
+ return qfalse;
+ }
+
+ if( !ps->valid )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to test an invalid particle system\n" );
+ return qfalse;
+ }
+
+ //don't bother checking already invalid systems
+ if( !ps->valid )
+ return qfalse;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+
+ if( pe->valid && pe->parent == ps )
+ {
+ if( pe->totalParticles == PARTICLES_INFINITE )
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_IsParticleSystemValid
+
+Test a particle system for validity
+===============
+*/
+qboolean CG_IsParticleSystemValid( particleSystem_t **ps )
+{
+ if( *ps == NULL || ( *ps && !(*ps)->valid ) )
+ {
+ if( *ps && !(*ps)->valid )
+ *ps = NULL;
+
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_GarbageCollectParticleSystems
+
+Destroy inactive particle systems
+===============
+*/
+static void CG_GarbageCollectParticleSystems( void )
+{
+ int i, j, count;
+ particleSystem_t *ps;
+ particleEjector_t *pe;
+ int centNum;
+
+ for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ )
+ {
+ ps = &particleSystems[ i ];
+ count = 0;
+
+ //don't bother checking already invalid systems
+ if( !ps->valid )
+ continue;
+
+ for( j = 0; j < MAX_PARTICLE_EJECTORS; j++ )
+ {
+ pe = &particleEjectors[ j ];
+
+ if( pe->valid && pe->parent == ps )
+ count++;
+ }
+
+ if( !count )
+ ps->valid = qfalse;
+
+ //check systems where the parent cent has left the PVS
+ //( local player entity is always valid )
+ if( ( centNum = CG_AttachmentCentNum( &ps->attachment ) ) >= 0 &&
+ centNum != cg.snap->ps.clientNum )
+ {
+ if( !cg_entities[ centNum ].valid )
+ ps->lazyRemove = qtrue;
+ }
+
+ if( cg_debugParticles.integer >= 1 && !ps->valid )
+ CG_Printf( "PS %s garbage collected\n", ps->class->name );
+ }
+}
+
+
+/*
+===============
+CG_CalculateTimeFrac
+
+Calculate the fraction of time passed
+===============
+*/
+static float CG_CalculateTimeFrac( int birth, int life, int delay )
+{
+ float frac;
+
+ frac = ( (float)cg.time - (float)( birth + delay ) ) / (float)( life - delay );
+
+ if( frac < 0.0f )
+ frac = 0.0f;
+ else if( frac > 1.0f )
+ frac = 1.0f;
+
+ return frac;
+}
+
+/*
+===============
+CG_EvaluateParticlePhysics
+
+Compute the physics on a specific particle
+===============
+*/
+static void CG_EvaluateParticlePhysics( particle_t *p )
+{
+ particleSystem_t *ps = p->parent->parent;
+ baseParticle_t *bp = p->class;
+ vec3_t acceleration, newOrigin;
+ vec3_t mins, maxs;
+ float deltaTime, bounce, radius, dot;
+ trace_t trace;
+ vec3_t transform[ 3 ];
+
+ if( p->atRest )
+ {
+ VectorClear( p->velocity );
+ return;
+ }
+
+ switch( bp->accMoveType )
+ {
+ case PMT_STATIC:
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ VectorSubtract( bp->accMoveValues.point, p->origin, acceleration );
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ VectorCopy( bp->accMoveValues.dir, acceleration );
+
+ break;
+
+ case PMT_STATIC_TRANSFORM:
+ if( !CG_AttachmentAxis( &ps->attachment, transform ) )
+ return;
+
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ {
+ vec3_t transPoint;
+
+ VectorMatrixMultiply( bp->accMoveValues.point, transform, transPoint );
+ VectorSubtract( transPoint, p->origin, acceleration );
+ }
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ VectorMatrixMultiply( bp->accMoveValues.dir, transform, acceleration );
+ break;
+
+ case PMT_TAG:
+ case PMT_CENT_ANGLES:
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ {
+ vec3_t point;
+
+ if( !CG_AttachmentPoint( &ps->attachment, point ) )
+ return;
+
+ VectorSubtract( point, p->origin, acceleration );
+ }
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ {
+ if( !CG_AttachmentDir( &ps->attachment, acceleration ) )
+ return;
+ }
+ break;
+
+ case PMT_NORMAL:
+ if( !ps->normalValid )
+ return;
+
+ VectorCopy( ps->normal, acceleration );
+
+ break;
+ }
+
+#define MAX_ACC_RADIUS 1000.0f
+
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ {
+ //FIXME: so this fall off is a bit... odd -- it works..
+ float r2 = DotProduct( acceleration, acceleration ); // = radius^2
+ float scale = ( MAX_ACC_RADIUS - r2 ) / MAX_ACC_RADIUS;
+
+ if( scale > 1.0f )
+ scale = 1.0f;
+ else if( scale < 0.1f )
+ scale = 0.1f;
+
+ scale *= CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac );
+
+ VectorNormalize( acceleration );
+ CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle );
+ VectorScale( acceleration, scale, acceleration );
+ }
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ {
+ VectorNormalize( acceleration );
+ CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle );
+ VectorScale( acceleration,
+ CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ),
+ acceleration );
+ }
+
+ radius = CG_LerpValues( p->radius.initial,
+ p->radius.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->radius.delay ) );
+
+ VectorSet( mins, -radius, -radius, -radius );
+ VectorSet( maxs, radius, radius, radius );
+
+ bounce = CG_RandomiseValue( bp->bounceFrac, bp->bounceFracRandFrac );
+
+ deltaTime = (float)( cg.time - p->lastEvalTime ) * 0.001;
+ VectorMA( p->velocity, deltaTime, acceleration, p->velocity );
+ VectorMA( p->origin, deltaTime, p->velocity, newOrigin );
+ p->lastEvalTime = cg.time;
+
+ // we're not doing particle physics, but at least cull them in solids
+ if( !cg_bounceParticles.integer )
+ {
+ int contents = trap_CM_PointContents( newOrigin, 0 );
+
+ if( ( contents & CONTENTS_SOLID ) || ( contents & CONTENTS_NODROP ) )
+ CG_DestroyParticle( p, NULL );
+ else
+ VectorCopy( newOrigin, p->origin );
+ return;
+ }
+
+ CG_Trace( &trace, p->origin, mins, maxs, newOrigin,
+ CG_AttachmentCentNum( &ps->attachment ), CONTENTS_SOLID );
+
+ //not hit anything or not a collider
+ if( trace.fraction == 1.0f || bounce == 0.0f )
+ {
+ VectorCopy( newOrigin, p->origin );
+ return;
+ }
+
+ //remove particles that get into a CONTENTS_NODROP brush
+ if( ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) ||
+ ( bp->cullOnStartSolid && trace.startsolid ) )
+ {
+ CG_DestroyParticle( p, NULL );
+ return;
+ }
+ else if( bp->bounceCull )
+ {
+ CG_DestroyParticle( p, trace.plane.normal );
+ return;
+ }
+
+ //reflect the velocity on the trace plane
+ dot = DotProduct( p->velocity, trace.plane.normal );
+ VectorMA( p->velocity, -2.0f * dot, trace.plane.normal, p->velocity );
+
+ VectorScale( p->velocity, bounce, p->velocity );
+
+ if( trace.plane.normal[ 2 ] > 0.5f &&
+ ( p->velocity[ 2 ] < 40.0f ||
+ p->velocity[ 2 ] < -cg.frametime * p->velocity[ 2 ] ) )
+ p->atRest = qtrue;
+
+ if( bp->bounceMarkName[ 0 ] && p->bounceMarkCount > 0 )
+ {
+ CG_ImpactMark( bp->bounceMark, trace.endpos, trace.plane.normal,
+ random( ) * 360, 1, 1, 1, 1, qtrue, bp->bounceMarkRadius, qfalse );
+ p->bounceMarkCount--;
+ }
+
+ if( bp->bounceSoundName[ 0 ] && p->bounceSoundCount > 0 )
+ {
+ trap_S_StartSound( trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, bp->bounceSound );
+ p->bounceSoundCount--;
+ }
+
+ VectorCopy( trace.endpos, p->origin );
+}
+
+
+#define GETKEY(x,y) (((x)>>y)&0xFF)
+
+/*
+===============
+CG_Radix
+===============
+*/
+static void CG_Radix( int bits, int size, particle_t **source, particle_t **dest )
+{
+ int count[ 256 ];
+ int index[ 256 ];
+ int i;
+
+ memset( count, 0, sizeof( count ) );
+
+ for( i = 0; i < size; i++ )
+ count[ GETKEY( source[ i ]->sortKey, bits ) ]++;
+
+ index[ 0 ] = 0;
+
+ for( i = 1; i < 256; i++ )
+ index[ i ] = index[ i - 1 ] + count[ i - 1 ];
+
+ for( i = 0; i < size; i++ )
+ dest[ index[ GETKEY( source[ i ]->sortKey, bits ) ]++ ] = source[ i ];
+}
+
+/*
+===============
+CG_RadixSort
+
+Radix sort with 4 byte size buckets
+===============
+*/
+static void CG_RadixSort( particle_t **source, particle_t **temp, int size )
+{
+ CG_Radix( 0, size, source, temp );
+ CG_Radix( 8, size, temp, source );
+ CG_Radix( 16, size, source, temp );
+ CG_Radix( 24, size, temp, source );
+}
+
+/*
+===============
+CG_CompactAndSortParticles
+
+Depth sort the particles
+===============
+*/
+static void CG_CompactAndSortParticles( void )
+{
+ int i, j = 0;
+ int numParticles;
+ vec3_t delta;
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ sortedParticles[ i ] = &particles[ i ];
+
+ if( !cg_depthSortParticles.integer )
+ return;
+
+ for( i = MAX_PARTICLES - 1; i >= 0; i-- )
+ {
+ if( sortedParticles[ i ]->valid )
+ {
+ //find the first hole
+ while( j < MAX_PARTICLES && sortedParticles[ j ]->valid )
+ j++;
+
+ //no more holes
+ if( j >= i )
+ break;
+
+ sortedParticles[ j ] = sortedParticles[ i ];
+ }
+ }
+
+ numParticles = i;
+
+ //set sort keys
+ for( i = 0; i < numParticles; i++ )
+ {
+ VectorSubtract( sortedParticles[ i ]->origin, cg.refdef.vieworg, delta );
+ sortedParticles[ i ]->sortKey = (int)DotProduct( delta, delta );
+ }
+
+ CG_RadixSort( sortedParticles, radixBuffer, numParticles );
+
+ //FIXME: wtf?
+ //reverse order of particles array
+ for( i = 0; i < numParticles; i++ )
+ radixBuffer[ i ] = sortedParticles[ numParticles - i - 1 ];
+
+ for( i = 0; i < numParticles; i++ )
+ sortedParticles[ i ] = radixBuffer[ i ];
+}
+
+/*
+===============
+CG_RenderParticle
+
+Actually render a particle
+===============
+*/
+static void CG_RenderParticle( particle_t *p )
+{
+ refEntity_t re;
+ float timeFrac, scale;
+ int index;
+ baseParticle_t *bp = p->class;
+ particleSystem_t *ps = p->parent->parent;
+ baseParticleSystem_t *bps = ps->class;
+ vec3_t alight, dlight, lightdir;
+ int i;
+ vec3_t up = { 0.0f, 0.0f, 1.0f };
+
+ memset( &re, 0, sizeof( refEntity_t ) );
+
+ timeFrac = CG_CalculateTimeFrac( p->birthTime, p->lifeTime, 0 );
+
+ scale = CG_LerpValues( p->radius.initial,
+ p->radius.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->radius.delay ) );
+
+ re.shaderTime = p->birthTime / 1000.0f;
+
+ if( bp->numFrames ) //shader based
+ {
+ re.reType = RT_SPRITE;
+
+ //apply environmental lighting to the particle
+ if( bp->realLight )
+ {
+ trap_R_LightForPoint( p->origin, alight, dlight, lightdir );
+ for( i = 0; i <= 2; i++ )
+ re.shaderRGBA[ i ] = (byte)alight[ i ];
+ }
+ else
+ {
+ vec3_t colorRange;
+
+ VectorSubtract( bp->finalColor,
+ bp->initialColor, colorRange );
+
+ VectorMA( bp->initialColor,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->colorDelay ),
+ colorRange, re.shaderRGBA );
+ }
+
+ re.shaderRGBA[ 3 ] = (byte)( (float)0xFF *
+ CG_LerpValues( p->alpha.initial,
+ p->alpha.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->alpha.delay ) ) );
+
+ re.radius = scale;
+
+ re.rotation = CG_LerpValues( p->rotation.initial,
+ p->rotation.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->rotation.delay ) );
+
+ // if the view would be "inside" the sprite, kill the sprite
+ // so it doesn't add too much overdraw
+ if( Distance( p->origin, cg.refdef.vieworg ) < re.radius && bp->overdrawProtection )
+ return;
+
+ if( bp->framerate == 0.0f )
+ {
+ //sync animation time to lifeTime of particle
+ index = (int)( timeFrac * ( bp->numFrames + 1 ) );
+
+ if( index >= bp->numFrames )
+ index = bp->numFrames - 1;
+
+ re.customShader = bp->shaders[ index ];
+ }
+ else
+ {
+ //looping animation
+ index = (int)( bp->framerate * timeFrac * p->lifeTime * 0.001 ) % bp->numFrames;
+ re.customShader = bp->shaders[ index ];
+ }
+
+ }
+ else if( bp->numModels ) //model based
+ {
+ re.reType = RT_MODEL;
+
+ re.hModel = p->model;
+
+ if( p->atRest )
+ AxisCopy( p->lastAxis, re.axis );
+ else
+ {
+ // convert direction of travel into axis
+ VectorNormalize2( p->velocity, re.axis[ 0 ] );
+
+ if( re.axis[ 0 ][ 0 ] == 0.0f && re.axis[ 0 ][ 1 ] == 0.0f )
+ AxisCopy( axisDefault, re.axis );
+ else
+ {
+ ProjectPointOnPlane( re.axis[ 2 ], up, re.axis[ 0 ] );
+ VectorNormalize( re.axis[ 2 ] );
+ CrossProduct( re.axis[ 2 ], re.axis[ 0 ], re.axis[ 1 ] );
+ }
+
+ AxisCopy( re.axis, p->lastAxis );
+ }
+
+ if( scale != 1.0f )
+ {
+ VectorScale( re.axis[ 0 ], scale, re.axis[ 0 ] );
+ VectorScale( re.axis[ 1 ], scale, re.axis[ 1 ] );
+ VectorScale( re.axis[ 2 ], scale, re.axis[ 2 ] );
+ re.nonNormalizedAxes = qtrue;
+ }
+ else
+ re.nonNormalizedAxes = qfalse;
+
+ p->lf.animation = &bp->modelAnimation;
+
+ //run animation
+ CG_RunLerpFrame( &p->lf );
+
+ re.oldframe = p->lf.oldFrame;
+ re.frame = p->lf.frame;
+ re.backlerp = p->lf.backlerp;
+ }
+
+ if( bps->thirdPersonOnly &&
+ CG_AttachmentCentNum( &ps->attachment ) == cg.snap->ps.clientNum &&
+ !cg.renderingThirdPerson )
+ re.renderfx |= RF_THIRD_PERSON;
+
+ if( bp->dynamicLight && !( re.renderfx & RF_THIRD_PERSON ) )
+ {
+ trap_R_AddLightToScene( p->origin,
+ CG_LerpValues( p->dLightRadius.initial, p->dLightRadius.final,
+ CG_CalculateTimeFrac( p->birthTime, p->lifeTime, p->dLightRadius.delay ) ),
+ (float)bp->dLightColor[ 0 ] / (float)0xFF,
+ (float)bp->dLightColor[ 1 ] / (float)0xFF,
+ (float)bp->dLightColor[ 2 ] / (float)0xFF );
+ }
+
+ VectorCopy( p->origin, re.origin );
+
+ trap_R_AddRefEntityToScene( &re );
+}
+
+/*
+===============
+CG_AddParticles
+
+Add particles to the scene
+===============
+*/
+void CG_AddParticles( void )
+{
+ int i;
+ particle_t *p;
+ int numPS = 0, numPE = 0, numP = 0;
+
+ //remove expired particle systems
+ CG_GarbageCollectParticleSystems( );
+
+ //check each ejector and introduce any new particles
+ CG_SpawnNewParticles( );
+
+ //sorting
+ CG_CompactAndSortParticles( );
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ {
+ p = sortedParticles[ i ];
+
+ if( p->valid )
+ {
+ if( p->birthTime + p->lifeTime > cg.time )
+ {
+ //particle is active
+ CG_EvaluateParticlePhysics( p );
+ CG_RenderParticle( p );
+ }
+ else
+ CG_DestroyParticle( p, NULL );
+ }
+ }
+
+ if( cg_debugParticles.integer >= 2 )
+ {
+ for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ )
+ if( particleSystems[ i ].valid )
+ numPS++;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ if( particleEjectors[ i ].valid )
+ numPE++;
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ if( particles[ i ].valid )
+ numP++;
+
+ CG_Printf( "PS: %d PE: %d P: %d\n", numPS, numPE, numP );
+ }
+}
+
+/*
+===============
+CG_ParticleSystemEntity
+
+Particle system entity client code
+===============
+*/
+void CG_ParticleSystemEntity( centity_t *cent )
+{
+ entityState_t *es;
+
+ es = &cent->currentState;
+
+ if( es->eFlags & EF_NODRAW )
+ {
+ if( CG_IsParticleSystemValid( &cent->entityPS ) && CG_IsParticleSystemInfinite( cent->entityPS ) )
+ CG_DestroyParticleSystem( &cent->entityPS );
+
+ return;
+ }
+
+ if( !CG_IsParticleSystemValid( &cent->entityPS ) && !cent->entityPSMissing )
+ {
+ cent->entityPS = CG_SpawnNewParticleSystem( cgs.gameParticleSystems[ es->modelindex ] );
+
+ if( CG_IsParticleSystemValid( &cent->entityPS ) )
+ {
+ CG_SetAttachmentPoint( &cent->entityPS->attachment, cent->lerpOrigin );
+ CG_SetAttachmentCent( &cent->entityPS->attachment, cent );
+ CG_AttachToPoint( &cent->entityPS->attachment );
+ }
+ else
+ cent->entityPSMissing = qtrue;
+ }
+}
+
+static particleSystem_t *testPS;
+static qhandle_t testPSHandle;
+
+/*
+===============
+CG_DestroyTestPS_f
+
+Destroy the test a particle system
+===============
+*/
+void CG_DestroyTestPS_f( void )
+{
+ if( CG_IsParticleSystemValid( &testPS ) )
+ CG_DestroyParticleSystem( &testPS );
+}
+
+/*
+===============
+CG_TestPS_f
+
+Test a particle system
+===============
+*/
+void CG_TestPS_f( void )
+{
+ vec3_t origin;
+ vec3_t up = { 0.0f, 0.0f, 1.0f };
+ char psName[ MAX_QPATH ];
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ Q_strncpyz( psName, CG_Argv( 1 ), MAX_QPATH );
+ testPSHandle = CG_RegisterParticleSystem( psName );
+
+ if( testPSHandle )
+ {
+ CG_DestroyTestPS_f( );
+
+ testPS = CG_SpawnNewParticleSystem( testPSHandle );
+
+ VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], origin );
+
+ if( CG_IsParticleSystemValid( &testPS ) )
+ {
+ CG_SetAttachmentPoint( &testPS->attachment, origin );
+ CG_SetParticleSystemNormal( testPS, up );
+ CG_AttachToPoint( &testPS->attachment );
+ }
+ }
+}
diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c
new file mode 100644
index 0000000..3bd0e65
--- /dev/null
+++ b/src/cgame/cg_players.c
@@ -0,0 +1,2565 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_players.c -- handle the media and animation for player entities
+
+
+#include "cg_local.h"
+
+char *cg_customSoundNames[ MAX_CUSTOM_SOUNDS ] =
+{
+ "*death1.wav",
+ "*death2.wav",
+ "*death3.wav",
+ "*jump1.wav",
+ "*pain25_1.wav",
+ "*pain50_1.wav",
+ "*pain75_1.wav",
+ "*pain100_1.wav",
+ "*falling1.wav",
+ "*gasp.wav",
+ "*drown.wav",
+ "*fall1.wav",
+ "*taunt.wav"
+};
+
+
+/*
+================
+CG_CustomSound
+
+================
+*/
+sfxHandle_t CG_CustomSound( int clientNum, const char *soundName )
+{
+ clientInfo_t *ci;
+ int i;
+
+ if( soundName[ 0 ] != '*' )
+ return trap_S_RegisterSound( soundName, qfalse );
+
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
+ clientNum = 0;
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ for( i = 0; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[ i ]; i++ )
+ {
+ if( !strcmp( soundName, cg_customSoundNames[ i ] ) )
+ return ci->sounds[ i ];
+ }
+
+ CG_Error( "Unknown custom sound: %s", soundName );
+ return 0;
+}
+
+
+
+/*
+=============================================================================
+
+CLIENT INFO
+
+=============================================================================
+*/
+
+/*
+======================
+CG_ParseAnimationFile
+
+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 )
+{
+ char *text_p, *prev;
+ int len;
+ int i;
+ char *token;
+ float fps;
+ int skip;
+ char text[ 20000 ];
+ fileHandle_t f;
+ animation_t *animations;
+
+ animations = ci->animations;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ trap_FS_FCloseFile( f );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+ skip = 0; // quite the compiler warning
+
+ ci->footsteps = FOOTSTEP_NORMAL;
+ VectorClear( ci->headOffset );
+ ci->gender = GENDER_MALE;
+ ci->fixedlegs = qfalse;
+ ci->fixedtorso = qfalse;
+ ci->nonsegmented = qfalse;
+
+ // read optional parameters
+ while( 1 )
+ {
+ prev = text_p; // so we can unget
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "footsteps" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) )
+ ci->footsteps = FOOTSTEP_NORMAL;
+ else if( !Q_stricmp( token, "flesh" ) )
+ ci->footsteps = FOOTSTEP_FLESH;
+ else if( !Q_stricmp( token, "none" ) )
+ ci->footsteps = FOOTSTEP_NONE;
+ else if( !Q_stricmp( token, "custom" ) )
+ ci->footsteps = FOOTSTEP_CUSTOM;
+ else
+ CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "headoffset" ) )
+ {
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ ci->headOffset[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "sex" ) )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( token[ 0 ] == 'f' || token[ 0 ] == 'F' )
+ ci->gender = GENDER_FEMALE;
+ else if( token[ 0 ] == 'n' || token[ 0 ] == 'N' )
+ ci->gender = GENDER_NEUTER;
+ else
+ ci->gender = GENDER_MALE;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "fixedlegs" ) )
+ {
+ ci->fixedlegs = qtrue;
+ continue;
+ }
+ else if( !Q_stricmp( token, "fixedtorso" ) )
+ {
+ ci->fixedtorso = qtrue;
+ continue;
+ }
+ else if( !Q_stricmp( token, "nonsegmented" ) )
+ {
+ ci->nonsegmented = qtrue;
+ continue;
+ }
+
+ // if it is a number, start parsing animations
+ if( token[ 0 ] >= '0' && token[ 0 ] <= '9' )
+ {
+ text_p = prev; // unget the token
+ break;
+ }
+
+ Com_Printf( "unknown token '%s' is %s\n", token, filename );
+ }
+
+ if( !ci->nonsegmented )
+ {
+ // read information for each frame
+ for( i = 0; i < MAX_PLAYER_ANIMATIONS; i++ )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ {
+ if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE )
+ {
+ animations[ i ].firstFrame = animations[ TORSO_GESTURE ].firstFrame;
+ animations[ i ].frameLerp = animations[ TORSO_GESTURE ].frameLerp;
+ animations[ i ].initialLerp = animations[ TORSO_GESTURE ].initialLerp;
+ animations[ i ].loopFrames = animations[ TORSO_GESTURE ].loopFrames;
+ animations[ i ].numFrames = animations[ TORSO_GESTURE ].numFrames;
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+ continue;
+ }
+
+ break;
+ }
+
+ animations[ i ].firstFrame = atoi( token );
+
+ // leg only frames are adjusted to not count the upper body only frames
+ if( i == LEGS_WALKCR )
+ skip = animations[ LEGS_WALKCR ].firstFrame - animations[ TORSO_GESTURE ].firstFrame;
+
+ if( i >= LEGS_WALKCR && i<TORSO_GETFLAG )
+ animations[ i ].firstFrame -= skip;
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].numFrames = atoi( token );
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( animations[ i ].numFrames < 0 )
+ {
+ animations[ i ].numFrames = -animations[ i ].numFrames;
+ animations[ i ].reversed = qtrue;
+ }
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ animations[ i ].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ fps = atof( token );
+ if( fps == 0 )
+ fps = 1;
+
+ animations[ i ].frameLerp = 1000 / fps;
+ animations[ i ].initialLerp = 1000 / fps;
+ }
+
+ if( i != MAX_PLAYER_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing animation file: %s", filename );
+ return qfalse;
+ }
+ // crouch backward animation
+ memcpy( &animations[ LEGS_BACKCR ], &animations[ LEGS_WALKCR ], sizeof( animation_t ) );
+ animations[ LEGS_BACKCR ].reversed = qtrue;
+ // walk backward animation
+ memcpy( &animations[ LEGS_BACKWALK ], &animations[ LEGS_WALK ], sizeof( animation_t ) );
+ animations[ LEGS_BACKWALK ].reversed = qtrue;
+ // flag moving fast
+ animations[ FLAG_RUN ].firstFrame = 0;
+ animations[ FLAG_RUN ].numFrames = 16;
+ animations[ FLAG_RUN ].loopFrames = 16;
+ animations[ FLAG_RUN ].frameLerp = 1000 / 15;
+ animations[ FLAG_RUN ].initialLerp = 1000 / 15;
+ animations[ FLAG_RUN ].reversed = qfalse;
+ // flag not moving or moving slowly
+ animations[ FLAG_STAND ].firstFrame = 16;
+ animations[ FLAG_STAND ].numFrames = 5;
+ animations[ FLAG_STAND ].loopFrames = 0;
+ animations[ FLAG_STAND ].frameLerp = 1000 / 20;
+ animations[ FLAG_STAND ].initialLerp = 1000 / 20;
+ animations[ FLAG_STAND ].reversed = qfalse;
+ // flag speeding up
+ animations[ FLAG_STAND2RUN ].firstFrame = 16;
+ animations[ FLAG_STAND2RUN ].numFrames = 5;
+ animations[ FLAG_STAND2RUN ].loopFrames = 1;
+ animations[ FLAG_STAND2RUN ].frameLerp = 1000 / 15;
+ animations[ FLAG_STAND2RUN ].initialLerp = 1000 / 15;
+ animations[ FLAG_STAND2RUN ].reversed = qtrue;
+ }
+ else
+ {
+ // read information for each frame
+ for( i = 0; i < MAX_NONSEG_PLAYER_ANIMATIONS; i++ )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ animations[ i ].firstFrame = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].numFrames = atoi( token );
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( animations[ i ].numFrames < 0 )
+ {
+ animations[ i ].numFrames = -animations[ i ].numFrames;
+ animations[ i ].reversed = qtrue;
+ }
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ animations[ i ].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ fps = atof( token );
+ if( fps == 0 )
+ fps = 1;
+
+ animations[ i ].frameLerp = 1000 / fps;
+ animations[ i ].initialLerp = 1000 / fps;
+ }
+
+ if( i != MAX_NONSEG_PLAYER_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing animation file: %s", filename );
+ return qfalse;
+ }
+
+ // walk backward animation
+ memcpy( &animations[ NSPA_WALKBACK ], &animations[ NSPA_WALK ], sizeof( animation_t ) );
+ animations[ NSPA_WALKBACK ].reversed = qtrue;
+ }
+
+ return qtrue;
+}
+
+/*
+==========================
+CG_RegisterClientSkin
+==========================
+*/
+static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *modelName, const char *skinName )
+{
+ char filename[ MAX_QPATH ];
+
+ if( !ci->nonsegmented )
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName );
+ ci->legsSkin = trap_R_RegisterSkin( filename );
+ if( !ci->legsSkin )
+ Com_Printf( "Leg skin load failure: %s\n", filename );
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName );
+ ci->torsoSkin = trap_R_RegisterSkin( filename );
+ if( !ci->torsoSkin )
+ Com_Printf( "Torso skin load failure: %s\n", filename );
+
+ 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 );
+
+ if( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin )
+ return qfalse;
+ }
+ else
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg_%s.skin", modelName, skinName );
+ ci->nonSegSkin = trap_R_RegisterSkin( filename );
+ if( !ci->nonSegSkin )
+ Com_Printf( "Non-segmented skin load failure: %s\n", filename );
+
+ if( !ci->nonSegSkin )
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+==========================
+CG_RegisterClientModelname
+==========================
+*/
+static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName )
+{
+ char filename[ MAX_QPATH * 2 ];
+
+ // do this first so the nonsegmented property is set
+ // load the animations
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
+ if( !CG_ParseAnimationFile( filename, ci ) )
+ {
+ Com_Printf( "Failed to load animation file %s\n", filename );
+ return qfalse;
+ }
+
+ // load cmodels before models so filecache works
+
+ if( !ci->nonsegmented )
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%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_sprintf( filename, sizeof( filename ), "models/players/%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_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", modelName );
+ ci->headModel = trap_R_RegisterModel( filename );
+ if( !ci->headModel )
+ {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+ else
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg.md3", modelName );
+ ci->nonSegModel = trap_R_RegisterModel( filename );
+ if( !ci->nonSegModel )
+ {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ // if any skins failed to load, return failure
+ if( !CG_RegisterClientSkin( ci, modelName, skinName ) )
+ {
+ Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
+ return qfalse;
+ }
+
+ //FIXME: skins do not load without icon present. do we want icons anyway?
+/* Com_sprintf( filename, sizeof( filename ), "models/players/%s/icon_%s.tga", modelName, skinName );
+ ci->modelIcon = trap_R_RegisterShaderNoMip( filename );
+ if( !ci->modelIcon )
+ {
+ Com_Printf( "Failed to load icon file: %s\n", filename );
+ return qfalse;
+ }*/
+
+ return qtrue;
+}
+
+/*
+====================
+CG_ColorFromString
+====================
+*/
+static void CG_ColorFromString( const char *v, vec3_t color )
+{
+ int val;
+
+ VectorClear( color );
+
+ val = atoi( v );
+
+ if( val < 1 || val > 7 )
+ {
+ VectorSet( color, 1, 1, 1 );
+ return;
+ }
+
+ if( val & 1 )
+ color[ 2 ] = 1.0f;
+
+ if( val & 2 )
+ color[ 1 ] = 1.0f;
+
+ if( val & 4 )
+ color[ 0 ] = 1.0f;
+}
+
+
+/*
+===================
+CG_LoadClientInfo
+
+Load it now, taking the disk hits
+===================
+*/
+static void CG_LoadClientInfo( clientInfo_t *ci )
+{
+ const char *dir, *fallback;
+ int i;
+ const char *s;
+ int clientNum;
+
+ if( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) )
+ {
+ if( cg_buildScript.integer )
+ CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName );
+
+ // fall back
+ if( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default" ) )
+ CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL );
+ }
+
+ // sounds
+ dir = ci->modelName;
+ fallback = DEFAULT_MODEL;
+
+ for( i = 0; i < MAX_CUSTOM_SOUNDS; i++ )
+ {
+ s = cg_customSoundNames[ i ];
+
+ if( !s )
+ break;
+
+ // fanny about a bit with sounds that are missing
+ if( !CG_FileExists( va( "sound/player/%s/%s", dir, s + 1 ) ) )
+ {
+ //file doesn't exist
+
+ if( i == 11 || i == 8 ) //fall or falling
+ {
+ ci->sounds[ i ] = trap_S_RegisterSound( "sound/null.wav", qfalse );
+ }
+ else
+ {
+ if( i == 9 ) //gasp
+ s = cg_customSoundNames[ 7 ]; //pain100_1
+ else if( i == 10 ) //drown
+ s = cg_customSoundNames[ 0 ]; //death1
+
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse );
+ if( !ci->sounds[ i ] )
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse );
+ }
+ }
+ else
+ {
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse );
+ if( !ci->sounds[ i ] )
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse );
+ }
+ }
+
+ if( ci->footsteps == FOOTSTEP_CUSTOM )
+ {
+ for( i = 0; i < 4; i++ )
+ {
+ ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/step%d.wav", dir, i + 1 ), qfalse );
+ if( !ci->customFootsteps[ i ] )
+ ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/step%d.wav", i + 1 ), qfalse );
+
+ ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/clank%d.wav", dir, i + 1 ), qfalse );
+ if( !ci->customMetalFootsteps[ i ] )
+ ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/clank%d.wav", i + 1 ), qfalse );
+ }
+ }
+
+ // reset any existing players and bodies, because they might be in bad
+ // frames for this new model
+ clientNum = ci - cgs.clientinfo;
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ if( cg_entities[ i ].currentState.clientNum == clientNum &&
+ cg_entities[ i ].currentState.eType == ET_PLAYER )
+ CG_ResetPlayerEntity( &cg_entities[ i ] );
+ }
+}
+
+/*
+======================
+CG_CopyClientInfoModel
+======================
+*/
+static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to )
+{
+ VectorCopy( from->headOffset, to->headOffset );
+ to->footsteps = from->footsteps;
+ to->gender = from->gender;
+
+ to->legsModel = from->legsModel;
+ to->legsSkin = from->legsSkin;
+ to->torsoModel = from->torsoModel;
+ to->torsoSkin = from->torsoSkin;
+ to->headModel = from->headModel;
+ to->headSkin = from->headSkin;
+ to->nonSegModel = from->nonSegModel;
+ to->nonSegSkin = from->nonSegSkin;
+ to->nonsegmented = from->nonsegmented;
+ to->modelIcon = from->modelIcon;
+
+ memcpy( to->animations, from->animations, sizeof( to->animations ) );
+ memcpy( to->sounds, from->sounds, sizeof( to->sounds ) );
+ memcpy( to->customFootsteps, from->customFootsteps, sizeof( to->customFootsteps ) );
+ memcpy( to->customMetalFootsteps, from->customMetalFootsteps, sizeof( to->customMetalFootsteps ) );
+}
+
+
+/*
+======================
+CG_GetCorpseNum
+======================
+*/
+static int CG_GetCorpseNum( pClass_t class )
+{
+ int i;
+ clientInfo_t *match;
+ char *modelName;
+ char *skinName;
+
+ modelName = BG_FindModelNameForClass( class );
+ skinName = BG_FindSkinNameForClass( class );
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ match = &cgs.corpseinfo[ i ];
+
+ if( !match->infoValid )
+ continue;
+
+ if( !Q_stricmp( modelName, match->modelName )
+ && !Q_stricmp( skinName, match->skinName ) )
+ {
+ // this clientinfo is identical, so use it's handles
+ return i;
+ }
+ }
+
+ //something has gone horribly wrong
+ return -1;
+}
+
+
+/*
+======================
+CG_ScanForExistingClientInfo
+======================
+*/
+static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci )
+{
+ int i;
+ clientInfo_t *match;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ match = &cgs.corpseinfo[ i ];
+
+ if( !match->infoValid )
+ continue;
+
+ if( !Q_stricmp( ci->modelName, match->modelName ) &&
+ !Q_stricmp( ci->skinName, match->skinName ) )
+ {
+ // this clientinfo is identical, so use it's handles
+ CG_CopyClientInfoModel( match, ci );
+
+ return qtrue;
+ }
+ }
+
+ // shouldn't happen
+ return qfalse;
+}
+
+
+/*
+======================
+CG_PrecacheClientInfo
+======================
+*/
+void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin )
+{
+ clientInfo_t *ci;
+ clientInfo_t newInfo;
+
+ ci = &cgs.corpseinfo[ class ];
+
+ // the old value
+ memset( &newInfo, 0, sizeof( newInfo ) );
+
+ // model
+ Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) );
+ Q_strncpyz( newInfo.headModelName, model, sizeof( newInfo.headModelName ) );
+
+ // modelName didn not include a skin name
+ if( !skin )
+ {
+ Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
+ Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) );
+ }
+ else
+ {
+ Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) );
+ Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) );
+ }
+
+ newInfo.infoValid = qtrue;
+
+ // actually register the models
+ *ci = newInfo;
+ CG_LoadClientInfo( ci );
+}
+
+
+/*
+======================
+CG_NewClientInfo
+======================
+*/
+void CG_NewClientInfo( int clientNum )
+{
+ clientInfo_t *ci;
+ clientInfo_t newInfo;
+ const char *configstring;
+ const char *v;
+ char *slash;
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ configstring = CG_ConfigString( clientNum + CS_PLAYERS );
+ if( !configstring[ 0 ] )
+ {
+ memset( ci, 0, sizeof( *ci ) );
+ return; // player just left
+ }
+
+ // the old value
+ memset( &newInfo, 0, sizeof( newInfo ) );
+
+ // isolate the player's name
+ v = Info_ValueForKey( configstring, "n" );
+ Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) );
+
+ // colors
+ v = Info_ValueForKey( configstring, "c1" );
+ CG_ColorFromString( v, newInfo.color1 );
+
+ v = Info_ValueForKey( configstring, "c2" );
+ CG_ColorFromString( v, newInfo.color2 );
+
+ // bot skill
+ v = Info_ValueForKey( configstring, "skill" );
+ newInfo.botSkill = atoi( v );
+
+ // handicap
+ v = Info_ValueForKey( configstring, "hc" );
+ newInfo.handicap = atoi( v );
+
+ // wins
+ v = Info_ValueForKey( configstring, "w" );
+ newInfo.wins = atoi( v );
+
+ // losses
+ v = Info_ValueForKey( configstring, "l" );
+ newInfo.losses = atoi( v );
+
+ // team
+ v = Info_ValueForKey( configstring, "t" );
+ newInfo.team = atoi( v );
+
+ // team task
+ v = Info_ValueForKey( configstring, "tt" );
+ newInfo.teamTask = atoi( v );
+
+ // team leader
+ v = Info_ValueForKey( configstring, "tl" );
+ newInfo.teamLeader = atoi( v );
+
+ // model
+ v = Info_ValueForKey( configstring, "model" );
+ Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) );
+
+ slash = strchr( newInfo.modelName, '/' );
+
+ if( !slash )
+ {
+ // modelName didn not include a skin name
+ Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
+ }
+ else
+ {
+ Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
+ // truncate modelName
+ *slash = 0;
+ }
+
+ //CG_Printf( "NCI: %s\n", v );
+
+ // head model
+ v = Info_ValueForKey( configstring, "hmodel" );
+ Q_strncpyz( newInfo.headModelName, v, 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.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) );
+ // truncate modelName
+ *slash = 0;
+ }
+
+ // replace whatever was there with the new one
+ newInfo.infoValid = qtrue;
+ *ci = newInfo;
+
+ // scan for an existing clientinfo that matches this modelname
+ // so we can avoid loading checks if possible
+ if( !CG_ScanForExistingClientInfo( ci ) )
+ CG_LoadClientInfo( ci );
+}
+
+
+
+/*
+=============================================================================
+
+PLAYER ANIMATION
+
+=============================================================================
+*/
+
+
+/*
+===============
+CG_SetLerpFrameAnimation
+
+may include ANIM_TOGGLEBIT
+===============
+*/
+static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation )
+{
+ animation_t *anim;
+
+ lf->animationNumber = newAnimation;
+ newAnimation &= ~ANIM_TOGGLEBIT;
+
+ if( newAnimation < 0 || newAnimation >= MAX_PLAYER_TOTALANIMATIONS )
+ CG_Error( "Bad animation number: %i", newAnimation );
+
+ anim = &ci->animations[ newAnimation ];
+
+ lf->animation = anim;
+ lf->animationTime = lf->frameTime + anim->initialLerp;
+
+ if( cg_debugAnim.integer )
+ CG_Printf( "Anim: %i\n", newAnimation );
+}
+
+/*
+===============
+CG_RunPlayerLerpFrame
+
+Sets cg.snap, cg.oldFrame, and cg.backlerp
+cg.time should be between oldFrameTime and frameTime after exit
+===============
+*/
+static void CG_RunPlayerLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale )
+{
+ int f, numFrames;
+ animation_t *anim;
+
+ // debugging tool to get no animations
+ if( cg_animSpeed.integer == 0 )
+ {
+ lf->oldFrame = lf->frame = lf->backlerp = 0;
+ return;
+ }
+
+ // see if the animation sequence is switching
+ if( newAnimation != lf->animationNumber || !lf->animation )
+ {
+ CG_SetLerpFrameAnimation( ci, lf, newAnimation );
+ }
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if( cg.time >= lf->frameTime )
+ {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if( !anim->frameLerp )
+ return; // shouldn't happen
+
+ if( cg.time < lf->animationTime )
+ lf->frameTime = lf->animationTime; // initial lerp
+ else
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ f *= speedScale; // adjust for haste, etc
+ numFrames = anim->numFrames;
+
+ if( anim->flipflop )
+ numFrames *= 2;
+
+ if( f >= numFrames )
+ {
+ f -= numFrames;
+ if( anim->loopFrames )
+ {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ }
+ else
+ {
+ f = numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = cg.time;
+ }
+ }
+
+ if( anim->reversed )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
+ else if( anim->flipflop && f>=anim->numFrames )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames );
+ else
+ lf->frame = anim->firstFrame + f;
+
+ if( cg.time > lf->frameTime )
+ {
+ lf->frameTime = cg.time;
+
+ if( cg_debugAnim.integer )
+ CG_Printf( "Clamp lf->frameTime\n" );
+ }
+ }
+
+ if( lf->frameTime > cg.time + 200 )
+ lf->frameTime = cg.time;
+
+ if( lf->oldFrameTime > cg.time )
+ lf->oldFrameTime = cg.time;
+
+ // calculate current lerp value
+ if( lf->frameTime == lf->oldFrameTime )
+ lf->backlerp = 0;
+ else
+ lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+}
+
+
+/*
+===============
+CG_ClearLerpFrame
+===============
+*/
+static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber )
+{
+ lf->frameTime = lf->oldFrameTime = cg.time;
+ CG_SetLerpFrameAnimation( ci, lf, animationNumber );
+ lf->oldFrame = lf->frame = lf->animation->firstFrame;
+}
+
+
+/*
+===============
+CG_PlayerAnimation
+===============
+*/
+static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp,
+ int *torsoOld, int *torso, float *torsoBackLerp )
+{
+ clientInfo_t *ci;
+ int clientNum;
+ float speedScale = 1.0f;
+
+ clientNum = cent->currentState.clientNum;
+
+ if( cg_noPlayerAnims.integer )
+ {
+ *legsOld = *legs = *torsoOld = *torso = 0;
+ return;
+ }
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ // do the shuffle turn frames locally
+ if( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE )
+ CG_RunPlayerLerpFrame( ci, &cent->pe.legs, LEGS_TURN, speedScale );
+ else
+ CG_RunPlayerLerpFrame( ci, &cent->pe.legs, cent->currentState.legsAnim, speedScale );
+
+ *legsOld = cent->pe.legs.oldFrame;
+ *legs = cent->pe.legs.frame;
+ *legsBackLerp = cent->pe.legs.backlerp;
+
+ CG_RunPlayerLerpFrame( ci, &cent->pe.torso, cent->currentState.torsoAnim, speedScale );
+
+ *torsoOld = cent->pe.torso.oldFrame;
+ *torso = cent->pe.torso.frame;
+ *torsoBackLerp = cent->pe.torso.backlerp;
+}
+
+
+/*
+===============
+CG_PlayerNonSegAnimation
+===============
+*/
+static void CG_PlayerNonSegAnimation( centity_t *cent, int *nonSegOld,
+ int *nonSeg, float *nonSegBackLerp )
+{
+ clientInfo_t *ci;
+ int clientNum;
+ float speedScale = 1.0f;
+
+ clientNum = cent->currentState.clientNum;
+
+ if( cg_noPlayerAnims.integer )
+ {
+ *nonSegOld = *nonSeg = 0;
+ return;
+ }
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ // do the shuffle turn frames locally
+ if( cent->pe.nonseg.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == NSPA_STAND )
+ CG_RunPlayerLerpFrame( ci, &cent->pe.nonseg, NSPA_TURN, speedScale );
+ else
+ CG_RunPlayerLerpFrame( ci, &cent->pe.nonseg, cent->currentState.legsAnim, speedScale );
+
+ *nonSegOld = cent->pe.nonseg.oldFrame;
+ *nonSeg = cent->pe.nonseg.frame;
+ *nonSegBackLerp = cent->pe.nonseg.backlerp;
+}
+
+/*
+=============================================================================
+
+PLAYER ANGLES
+
+=============================================================================
+*/
+
+/*
+==================
+CG_SwingAngles
+==================
+*/
+static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance,
+ float speed, float *angle, qboolean *swinging )
+{
+ float swing;
+ float move;
+ float scale;
+
+ if( !*swinging )
+ {
+ // see if a swing should be started
+ swing = AngleSubtract( *angle, destination );
+
+ if( swing > swingTolerance || swing < -swingTolerance )
+ *swinging = qtrue;
+ }
+
+ if( !*swinging )
+ return;
+
+ // modify the speed depending on the delta
+ // so it doesn't seem so linear
+ swing = AngleSubtract( destination, *angle );
+ scale = fabs( swing );
+
+ if( scale < swingTolerance * 0.5 )
+ scale = 0.5;
+ else if( scale < swingTolerance )
+ scale = 1.0;
+ else
+ scale = 2.0;
+
+ // swing towards the destination angle
+ if( swing >= 0 )
+ {
+ move = cg.frametime * scale * speed;
+
+ if( move >= swing )
+ {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ }
+ else if( swing < 0 )
+ {
+ move = cg.frametime * scale * -speed;
+
+ if( move <= swing )
+ {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ }
+
+ // clamp to no more than tolerance
+ swing = AngleSubtract( destination, *angle );
+ if( swing > clampTolerance )
+ *angle = AngleMod( destination - ( clampTolerance - 1 ) );
+ else if( swing < -clampTolerance )
+ *angle = AngleMod( destination + ( clampTolerance - 1 ) );
+}
+
+/*
+=================
+CG_AddPainTwitch
+=================
+*/
+static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles )
+{
+ int t;
+ float f;
+
+ t = cg.time - cent->pe.painTime;
+
+ if( t >= PAIN_TWITCH_TIME )
+ return;
+
+ f = 1.0 - (float)t / PAIN_TWITCH_TIME;
+
+ if( cent->pe.painDirection )
+ torsoAngles[ ROLL ] += 20 * f;
+ else
+ torsoAngles[ ROLL ] -= 20 * f;
+}
+
+
+/*
+===============
+CG_PlayerAngles
+
+Handles seperate torso motion
+
+ legs pivot based on direction of movement
+
+ head always looks exactly at cent->lerpAngles
+
+ if motion < 20 degrees, show in head only
+ if < 45 degrees, also show in torso
+===============
+*/
+static void CG_PlayerAngles( centity_t *cent, vec3_t srcAngles,
+ vec3_t legs[ 3 ], vec3_t torso[ 3 ], vec3_t head[ 3 ] )
+{
+ vec3_t legsAngles, torsoAngles, headAngles;
+ float dest;
+ static int movementOffsets[ 8 ] = { 0, 22, 45, -22, 0, 22, -45, -22 };
+ vec3_t velocity;
+ float speed;
+ int dir, clientNum;
+ clientInfo_t *ci;
+
+ VectorCopy( srcAngles, headAngles );
+ headAngles[ YAW ] = AngleMod( headAngles[ YAW ] );
+ VectorClear( legsAngles );
+ VectorClear( torsoAngles );
+
+ // --------- yaw -------------
+
+ // allow yaw to drift a bit
+ if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE ||
+ ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND )
+ {
+ // 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
+ cent->pe.legs.yawing = qtrue; // always center
+ }
+
+ // adjust legs for movement dir
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ // don't let dead bodies twitch
+ dir = 0;
+ }
+ else
+ {
+ // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise
+ dir = cent->currentState.time2;
+ if( dir < 0 || dir > 7 )
+ CG_Error( "Bad player movement angle" );
+ }
+
+ legsAngles[ YAW ] = headAngles[ YAW ] + movementOffsets[ dir ];
+ torsoAngles[ YAW ] = headAngles[ YAW ] + 0.25 * movementOffsets[ dir ];
+
+ // torso
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ CG_SwingAngles( torsoAngles[ YAW ], 0, 0, cg_swingSpeed.value,
+ &cent->pe.torso.yawAngle, &cent->pe.torso.yawing );
+ CG_SwingAngles( legsAngles[ YAW ], 0, 0, cg_swingSpeed.value,
+ &cent->pe.legs.yawAngle, &cent->pe.legs.yawing );
+ }
+ else
+ {
+ CG_SwingAngles( torsoAngles[ YAW ], 25, 90, cg_swingSpeed.value,
+ &cent->pe.torso.yawAngle, &cent->pe.torso.yawing );
+ CG_SwingAngles( legsAngles[ YAW ], 40, 90, cg_swingSpeed.value,
+ &cent->pe.legs.yawAngle, &cent->pe.legs.yawing );
+ }
+
+ torsoAngles[ YAW ] = cent->pe.torso.yawAngle;
+ legsAngles[ YAW ] = cent->pe.legs.yawAngle;
+
+ // --------- pitch -------------
+
+ // only show a fraction of the pitch angle in the torso
+ if( headAngles[ PITCH ] > 180 )
+ dest = ( -360 + headAngles[ PITCH ] ) * 0.75f;
+ else
+ dest = headAngles[ PITCH ] * 0.75f;
+
+ CG_SwingAngles( dest, 15, 30, 0.1f, &cent->pe.torso.pitchAngle, &cent->pe.torso.pitching );
+ torsoAngles[ PITCH ] = cent->pe.torso.pitchAngle;
+
+ //
+ clientNum = cent->currentState.clientNum;
+
+ if( clientNum >= 0 && clientNum < MAX_CLIENTS )
+ {
+ ci = &cgs.clientinfo[ clientNum ];
+ if( ci->fixedtorso )
+ torsoAngles[ PITCH ] = 0.0f;
+ }
+
+ // --------- roll -------------
+
+
+ // lean towards the direction of travel
+ VectorCopy( cent->currentState.pos.trDelta, velocity );
+ speed = VectorNormalize( velocity );
+
+ if( speed )
+ {
+ vec3_t axis[ 3 ];
+ float side;
+
+ speed *= 0.05f;
+
+ AnglesToAxis( legsAngles, axis );
+ side = speed * DotProduct( velocity, axis[ 1 ] );
+ legsAngles[ ROLL ] -= side;
+
+ side = speed * DotProduct( velocity, axis[ 0 ] );
+ legsAngles[ PITCH ] += side;
+ }
+
+ //
+ clientNum = cent->currentState.clientNum;
+
+ if( clientNum >= 0 && clientNum < MAX_CLIENTS )
+ {
+ ci = &cgs.clientinfo[ clientNum ];
+
+ if( ci->fixedlegs )
+ {
+ legsAngles[ YAW ] = torsoAngles[ YAW ];
+ legsAngles[ PITCH ] = 0.0f;
+ legsAngles[ ROLL ] = 0.0f;
+ }
+ }
+
+ // pain twitch
+ CG_AddPainTwitch( cent, torsoAngles );
+
+ // pull the angles back out of the hierarchial chain
+ AnglesSubtract( headAngles, torsoAngles, headAngles );
+ AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
+ AnglesToAxis( legsAngles, legs );
+ AnglesToAxis( torsoAngles, torso );
+ AnglesToAxis( headAngles, head );
+}
+
+#define MODEL_WWSMOOTHTIME 200
+
+/*
+===============
+CG_PlayerWWSmoothing
+
+Smooth the angles of transitioning wall walkers
+===============
+*/
+static void CG_PlayerWWSmoothing( centity_t *cent, vec3_t in[ 3 ], vec3_t out[ 3 ] )
+{
+ entityState_t *es = &cent->currentState;
+ int i;
+ vec3_t surfNormal, rotAxis, temp;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+ float stLocal, sFraction, rotAngle;
+ vec3_t inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ];
+
+ //set surfNormal
+ if( !(es->eFlags & EF_WALLCLIMB ) )
+ VectorCopy( refNormal, surfNormal );
+ else if( !( es->eFlags & EF_WALLCLIMBCEILING ) )
+ VectorCopy( es->angles2, surfNormal );
+ else
+ VectorCopy( ceilingNormal, surfNormal );
+
+ AxisCopy( in, inAxis );
+
+ if( !VectorCompare( surfNormal, cent->pe.lastNormal ) )
+ {
+ //if we moving from the ceiling to the floor special case
+ //( x product of colinear vectors is undefined)
+ if( VectorCompare( ceilingNormal, cent->pe.lastNormal ) &&
+ VectorCompare( refNormal, surfNormal ) )
+ {
+ VectorCopy( in[ 1 ], rotAxis );
+ rotAngle = 180.0f;
+ }
+ else
+ {
+ AxisCopy( cent->pe.lastAxis, lastAxis );
+ rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) +
+ DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) +
+ DotProduct( inAxis[ 2 ], lastAxis[ 2 ] );
+
+ rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) );
+
+ CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp );
+ VectorCopy( temp, rotAxis );
+ CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+ CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+
+ VectorNormalize( rotAxis );
+ }
+
+ //iterate through smooth array
+ for( i = 0; i < MAXSMOOTHS; i++ )
+ {
+ //found an unused index in the smooth array
+ if( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME < cg.time )
+ {
+ //copy to array and stop
+ VectorCopy( rotAxis, cent->pe.sList[ i ].rotAxis );
+ cent->pe.sList[ i ].rotAngle = rotAngle;
+ cent->pe.sList[ i ].time = cg.time;
+ break;
+ }
+ }
+ }
+
+ //iterate through ops
+ for( i = MAXSMOOTHS - 1; i >= 0; i-- )
+ {
+ //if this op has time remaining, perform it
+ if( cg.time < cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME )
+ {
+ stLocal = 1.0f - ( ( ( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME ) - cg.time ) / MODEL_WWSMOOTHTIME );
+ sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f;
+
+ RotatePointAroundVector( outAxis[ 0 ], cent->pe.sList[ i ].rotAxis,
+ inAxis[ 0 ], sFraction * cent->pe.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 1 ], cent->pe.sList[ i ].rotAxis,
+ inAxis[ 1 ], sFraction * cent->pe.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 2 ], cent->pe.sList[ i ].rotAxis,
+ inAxis[ 2 ], sFraction * cent->pe.sList[ i ].rotAngle );
+
+ AxisCopy( outAxis, inAxis );
+ }
+ }
+
+ //outAxis has been copied to inAxis
+ AxisCopy( inAxis, out );
+}
+
+/*
+===============
+CG_PlayerNonSegAngles
+
+Resolve angles for non-segmented models
+===============
+*/
+static void CG_PlayerNonSegAngles( centity_t *cent, vec3_t srcAngles, vec3_t nonSegAxis[ 3 ] )
+{
+ vec3_t localAngles;
+ vec3_t velocity;
+ float speed;
+ int dir;
+ entityState_t *es = &cent->currentState;
+ vec3_t surfNormal;
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+
+ VectorCopy( srcAngles, localAngles );
+ localAngles[ YAW ] = AngleMod( localAngles[ YAW ] );
+ localAngles[ PITCH ] = 0.0f;
+ localAngles[ ROLL ] = 0.0f;
+
+ //set surfNormal
+ if( !( es->eFlags & EF_WALLCLIMBCEILING ) )
+ VectorCopy( es->angles2, surfNormal );
+ else
+ VectorCopy( ceilingNormal, surfNormal );
+
+ //make sure that WW transitions don't cause the swing stuff to go nuts
+ if( !VectorCompare( surfNormal, cent->pe.lastNormal ) )
+ {
+ //stop CG_SwingAngles having an eppy
+ cent->pe.nonseg.yawAngle = localAngles[ YAW ];
+ cent->pe.nonseg.yawing = qfalse;
+ }
+
+ // --------- yaw -------------
+
+ // allow yaw to drift a bit
+ if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != NSPA_STAND )
+ {
+ // if not standing still, always point all in the same direction
+ cent->pe.nonseg.yawing = qtrue; // always center
+ }
+
+ // adjust legs for movement dir
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ // don't let dead bodies twitch
+ dir = 0;
+ }
+ else
+ {
+ // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise
+ dir = cent->currentState.time2;
+ if( dir < 0 || dir > 7 )
+ CG_Error( "Bad player movement angle" );
+ }
+
+ // torso
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ CG_SwingAngles( localAngles[ YAW ], 0, 0, cg_swingSpeed.value,
+ &cent->pe.nonseg.yawAngle, &cent->pe.nonseg.yawing );
+ }
+ else
+ {
+ CG_SwingAngles( localAngles[ YAW ], 40, 90, cg_swingSpeed.value,
+ &cent->pe.nonseg.yawAngle, &cent->pe.nonseg.yawing );
+ }
+
+ localAngles[ YAW ] = cent->pe.nonseg.yawAngle;
+
+ // --------- pitch -------------
+
+ //NO PITCH!
+
+
+ // --------- roll -------------
+
+
+ // lean towards the direction of travel
+ VectorCopy( cent->currentState.pos.trDelta, velocity );
+ speed = VectorNormalize( velocity );
+
+ if( speed )
+ {
+ vec3_t axis[ 3 ];
+ float side;
+
+ //much less than with the regular model system
+ speed *= 0.01f;
+
+ AnglesToAxis( localAngles, axis );
+ side = speed * DotProduct( velocity, axis[ 1 ] );
+ localAngles[ ROLL ] -= side;
+
+ side = speed * DotProduct( velocity, axis[ 0 ] );
+ localAngles[ PITCH ] += side;
+ }
+
+ //FIXME: PAIN[123] animations?
+ // pain twitch
+ //CG_AddPainTwitch( cent, torsoAngles );
+
+ AnglesToAxis( localAngles, nonSegAxis );
+}
+
+
+//==========================================================================
+
+/*
+===============
+CG_PlayerUpgrade
+===============
+*/
+static void CG_PlayerUpgrades( centity_t *cent, refEntity_t *torso )
+{
+ int held, active;
+ refEntity_t jetpack;
+ refEntity_t battpack;
+ refEntity_t flash;
+ entityState_t *es = &cent->currentState;
+
+ held = es->modelindex;
+ active = es->modelindex2;
+
+ if( held & ( 1 << UP_JETPACK ) )
+ {
+ memset( &jetpack, 0, sizeof( jetpack ) );
+ VectorCopy( torso->lightingOrigin, jetpack.lightingOrigin );
+ jetpack.shadowPlane = torso->shadowPlane;
+ jetpack.renderfx = torso->renderfx;
+
+ jetpack.hModel = cgs.media.jetpackModel;
+
+ //identity matrix
+ AxisCopy( axisDefault, jetpack.axis );
+
+ //FIXME: change to tag_back when it exists
+ CG_PositionRotatedEntityOnTag( &jetpack, torso, torso->hModel, "tag_head" );
+
+ trap_R_AddRefEntityToScene( &jetpack );
+
+ if( active & ( 1 << UP_JETPACK ) )
+ {
+ if( es->pos.trDelta[ 2 ] > 10.0f )
+ {
+ if( cent->jetPackState != JPS_ASCENDING )
+ {
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+
+ cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackAscendPS );
+ cent->jetPackState = JPS_ASCENDING;
+ }
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
+ vec3_origin, cgs.media.jetpackAscendSound );
+ }
+ else if( es->pos.trDelta[ 2 ] < -10.0f )
+ {
+ if( cent->jetPackState != JPS_DESCENDING )
+ {
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+
+ cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackDescendPS );
+ cent->jetPackState = JPS_DESCENDING;
+ }
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
+ vec3_origin, cgs.media.jetpackDescendSound );
+ }
+ else
+ {
+ if( cent->jetPackState != JPS_HOVERING )
+ {
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+
+ cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackHoverPS );
+ cent->jetPackState = JPS_HOVERING;
+ }
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
+ vec3_origin, cgs.media.jetpackIdleSound );
+ }
+
+ memset( &flash, 0, sizeof( flash ) );
+ VectorCopy( torso->lightingOrigin, flash.lightingOrigin );
+ flash.shadowPlane = torso->shadowPlane;
+ flash.renderfx = torso->renderfx;
+
+ flash.hModel = cgs.media.jetpackFlashModel;
+ if( !flash.hModel )
+ return;
+
+ AxisCopy( axisDefault, flash.axis );
+
+ CG_PositionRotatedEntityOnTag( &flash, &jetpack, jetpack.hModel, "tag_flash" );
+ trap_R_AddRefEntityToScene( &flash );
+
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ {
+ CG_SetAttachmentTag( &cent->jetPackPS->attachment,
+ jetpack, jetpack.hModel, "tag_flash" );
+ CG_SetAttachmentCent( &cent->jetPackPS->attachment, cent );
+ CG_AttachToTag( &cent->jetPackPS->attachment );
+ }
+ }
+ else if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ {
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+ cent->jetPackState = JPS_OFF;
+ }
+ }
+ else if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ {
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+ cent->jetPackState = JPS_OFF;
+ }
+
+ if( held & ( 1 << UP_BATTPACK ) )
+ {
+ memset( &battpack, 0, sizeof( battpack ) );
+ VectorCopy( torso->lightingOrigin, battpack.lightingOrigin );
+ battpack.shadowPlane = torso->shadowPlane;
+ battpack.renderfx = torso->renderfx;
+
+ battpack.hModel = cgs.media.battpackModel;
+
+ //identity matrix
+ AxisCopy( axisDefault, battpack.axis );
+
+ //FIXME: change to tag_back when it exists
+ CG_PositionRotatedEntityOnTag( &battpack, torso, torso->hModel, "tag_head" );
+
+ trap_R_AddRefEntityToScene( &battpack );
+ }
+
+ if( es->eFlags & EF_BLOBLOCKED )
+ {
+ vec3_t temp, origin, up = { 0.0f, 0.0f, 1.0f };
+ trace_t tr;
+ float size;
+
+ VectorCopy( es->pos.trBase, temp );
+ temp[ 2 ] -= 4096.0f;
+
+ CG_Trace( &tr, es->pos.trBase, NULL, NULL, temp, es->number, MASK_SOLID );
+ VectorCopy( tr.endpos, origin );
+
+ size = 32.0f;
+
+ if( size > 0.0f )
+ CG_ImpactMark( cgs.media.creepShader, origin, up,
+ 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue );
+ }
+}
+
+
+/*
+===============
+CG_PlayerFloatSprite
+
+Float a sprite over the player's head
+===============
+*/
+static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader )
+{
+ int rf;
+ refEntity_t ent;
+
+ if( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )
+ rf = RF_THIRD_PERSON; // only show in mirrors
+ else
+ rf = 0;
+
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ ent.origin[ 2 ] += 48;
+ ent.reType = RT_SPRITE;
+ ent.customShader = shader;
+ ent.radius = 10;
+ ent.renderfx = rf;
+ ent.shaderRGBA[ 0 ] = 255;
+ ent.shaderRGBA[ 1 ] = 255;
+ ent.shaderRGBA[ 2 ] = 255;
+ ent.shaderRGBA[ 3 ] = 255;
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+
+/*
+===============
+CG_PlayerSprites
+
+Float sprites over the player's head
+===============
+*/
+static void CG_PlayerSprites( centity_t *cent )
+{
+ if( cent->currentState.eFlags & EF_CONNECTION )
+ {
+ CG_PlayerFloatSprite( cent, cgs.media.connectionShader );
+ return;
+ }
+
+ if( cent->currentState.eFlags & EF_TALK )
+ {
+ // the masses have decreed this to be wrong
+/* CG_PlayerFloatSprite( cent, cgs.media.balloonShader );
+ return;*/
+ }
+}
+
+/*
+===============
+CG_PlayerShadow
+
+Returns the Z component of the surface being shadowed
+
+ should it return a full plane instead of a Z?
+===============
+*/
+#define SHADOW_DISTANCE 128
+static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t class )
+{
+ vec3_t end, mins, maxs;
+ trace_t trace;
+ float alpha;
+ entityState_t *es = &cent->currentState;
+ vec3_t surfNormal = { 0.0f, 0.0f, 1.0f };
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+ mins[ 2 ] = 0.0f;
+ maxs[ 2 ] = 2.0f;
+
+ if( es->eFlags & EF_WALLCLIMB )
+ {
+ if( es->eFlags & EF_WALLCLIMBCEILING )
+ VectorSet( surfNormal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( es->angles2, surfNormal );
+ }
+
+ *shadowPlane = 0;
+
+ if( cg_shadows.integer == 0 )
+ return qfalse;
+
+ // send a trace down from the player to the ground
+ VectorCopy( cent->lerpOrigin, end );
+ VectorMA( cent->lerpOrigin, -SHADOW_DISTANCE, surfNormal, end );
+
+ trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
+
+ // no shadow if too high
+ if( trace.fraction == 1.0 || trace.startsolid || trace.allsolid )
+ return qfalse;
+
+ // FIXME: stencil shadows will be broken for walls.
+ // Unfortunately there isn't much that can be
+ // done since Q3 references only the Z coord
+ // of the shadowPlane
+ if( surfNormal[ 2 ] < 0.0f )
+ *shadowPlane = trace.endpos[ 2 ] - 1.0f;
+ else
+ *shadowPlane = trace.endpos[ 2 ] + 1.0f;
+
+ if( cg_shadows.integer != 1 ) // no mark for stencil or projection shadows
+ return qtrue;
+
+ // fade the shadow out with height
+ alpha = 1.0 - trace.fraction;
+
+ // 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, 0.0f, 0.0f, 0.0f, alpha, qfalse,
+ 24.0f * BG_FindShadowScaleForClass( class ), qtrue );
+
+ return qtrue;
+}
+
+
+/*
+===============
+CG_PlayerSplash
+
+Draw a mark at the water surface
+===============
+*/
+static void CG_PlayerSplash( centity_t *cent, pClass_t class )
+{
+ vec3_t start, end;
+ vec3_t mins, maxs;
+ trace_t trace;
+ int contents;
+
+ if( !cg_shadows.integer )
+ return;
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+
+ VectorCopy( cent->lerpOrigin, end );
+ end[ 2 ] += mins[ 2 ];
+
+ // if the feet aren't in liquid, don't make a mark
+ // this won't handle moving water brushes, but they wouldn't draw right anyway...
+ contents = trap_CM_PointContents( end, 0 );
+
+ if( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) )
+ return;
+
+ VectorCopy( cent->lerpOrigin, start );
+ start[ 2 ] += 32;
+
+ // if the head isn't out of liquid, don't make a mark
+ contents = trap_CM_PointContents( start, 0 );
+
+ if( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
+ return;
+
+ // trace down to find the surface
+ trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0,
+ ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) );
+
+ if( trace.fraction == 1.0f )
+ return;
+
+ CG_ImpactMark( cgs.media.wakeMarkShader, trace.endpos, trace.plane.normal,
+ cent->pe.legs.yawAngle, 1.0f, 1.0f, 1.0f, 1.0f, qfalse,
+ 32.0f * BG_FindShadowScaleForClass( class ), qtrue );
+}
+
+
+/*
+=================
+CG_LightVerts
+=================
+*/
+int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts )
+{
+ int i, j;
+ float incoming;
+ vec3_t ambientLight;
+ vec3_t lightDir;
+ vec3_t directedLight;
+
+ trap_R_LightForPoint( verts[ 0 ].xyz, ambientLight, directedLight, lightDir );
+
+ for( i = 0; i < numVerts; i++ )
+ {
+ incoming = DotProduct( normal, lightDir );
+
+ if( incoming <= 0 )
+ {
+ verts[ i ].modulate[ 0 ] = ambientLight[ 0 ];
+ verts[ i ].modulate[ 1 ] = ambientLight[ 1 ];
+ verts[ i ].modulate[ 2 ] = ambientLight[ 2 ];
+ verts[ i ].modulate[ 3 ] = 255;
+ continue;
+ }
+
+ j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ verts[ i ].modulate[ 0 ] = j;
+
+ j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ verts[ i ].modulate[ 1 ] = j;
+
+ j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ verts[ i ].modulate[ 2 ] = j;
+
+ verts[ i ].modulate[ 3 ] = 255;
+ }
+ return qtrue;
+}
+
+
+/*
+=================
+CG_LightFromDirection
+=================
+*/
+int CG_LightFromDirection( vec3_t point, vec3_t direction )
+{
+ int j;
+ float incoming;
+ vec3_t ambientLight;
+ vec3_t lightDir;
+ vec3_t directedLight;
+ vec3_t result;
+
+ trap_R_LightForPoint( point, ambientLight, directedLight, lightDir );
+
+ incoming = DotProduct( direction, lightDir );
+
+ if( incoming <= 0 )
+ {
+ result[ 0 ] = ambientLight[ 0 ];
+ result[ 1 ] = ambientLight[ 1 ];
+ result[ 2 ] = ambientLight[ 2 ];
+ return (int)( (float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f );
+ }
+
+ j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ result[ 0 ] = j;
+
+ j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ result[ 1 ] = j;
+
+ j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ result[ 2 ] = j;
+
+ return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f );
+}
+
+
+/*
+=================
+CG_AmbientLight
+=================
+*/
+int CG_AmbientLight( vec3_t point )
+{
+ vec3_t ambientLight;
+ vec3_t lightDir;
+ vec3_t directedLight;
+ vec3_t result;
+
+ trap_R_LightForPoint( point, ambientLight, directedLight, lightDir );
+
+ result[ 0 ] = ambientLight[ 0 ];
+ result[ 1 ] = ambientLight[ 1 ];
+ result[ 2 ] = ambientLight[ 2 ];
+ return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f );
+}
+
+#define TRACE_DEPTH 32.0f
+
+/*
+===============
+CG_Player
+===============
+*/
+void CG_Player( centity_t *cent )
+{
+ clientInfo_t *ci;
+
+ // NOTE: legs is used for nonsegmented models
+ // this helps reduce code to be changed
+ refEntity_t legs;
+ refEntity_t torso;
+ refEntity_t head;
+ int clientNum;
+ int renderfx;
+ qboolean shadow = qfalse;
+ float shadowPlane = 0.0f;
+ entityState_t *es = &cent->currentState;
+ pClass_t class = ( es->misc >> 8 ) & 0xFF;
+ float scale;
+ vec3_t tempAxis[ 3 ], tempAxis2[ 3 ];
+ vec3_t angles;
+ int held = es->modelindex;
+ vec3_t surfNormal = { 0.0f, 0.0f, 1.0f };
+
+ // the client number is stored in clientNum. It can't be derived
+ // from the entity number, because a single client may have
+ // multiple corpses on the level using the same clientinfo
+ clientNum = es->clientNum;
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
+ CG_Error( "Bad clientNum on player entity" );
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ // it is possible to see corpses from disconnected players that may
+ // not have valid clientinfo
+ if( !ci->infoValid )
+ return;
+
+ //don't draw
+ if( es->eFlags & EF_NODRAW )
+ return;
+
+ // get the player model information
+ renderfx = 0;
+ if( es->number == cg.snap->ps.clientNum )
+ {
+ if( !cg.renderingThirdPerson )
+ renderfx = RF_THIRD_PERSON; // only draw in mirrors
+ else if( cg_cameraMode.integer )
+ return;
+ }
+
+ if( cg_drawBBOX.integer )
+ {
+ vec3_t mins, maxs;
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+ CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs );
+ }
+
+ memset( &legs, 0, sizeof( legs ) );
+ memset( &torso, 0, sizeof( torso ) );
+ memset( &head, 0, sizeof( head ) );
+
+ VectorCopy( cent->lerpAngles, angles );
+ AnglesToAxis( cent->lerpAngles, tempAxis );
+
+ //rotate lerpAngles to floor
+ if( es->eFlags & EF_WALLCLIMB &&
+ BG_RotateAxis( es->angles2, tempAxis, tempAxis2, qtrue, es->eFlags & EF_WALLCLIMBCEILING ) )
+ AxisToAngles( tempAxis2, angles );
+ else
+ VectorCopy( cent->lerpAngles, angles );
+
+ //normalise the pitch
+ if( angles[ PITCH ] < -180.0f )
+ angles[ PITCH ] += 360.0f;
+
+ // get the rotation information
+ if( !ci->nonsegmented )
+ CG_PlayerAngles( cent, angles, legs.axis, torso.axis, head.axis );
+ else
+ CG_PlayerNonSegAngles( cent, angles, legs.axis );
+
+ AxisCopy( legs.axis, tempAxis );
+
+ //rotate the legs axis to back to the wall
+ if( es->eFlags & EF_WALLCLIMB &&
+ BG_RotateAxis( es->angles2, legs.axis, tempAxis, qfalse, es->eFlags & EF_WALLCLIMBCEILING ) )
+ AxisCopy( tempAxis, legs.axis );
+
+ //smooth out WW transitions so the model doesn't hop around
+ CG_PlayerWWSmoothing( cent, legs.axis, legs.axis );
+
+ AxisCopy( tempAxis, cent->pe.lastAxis );
+
+ // get the animation state (after rotation, to allow feet shuffle)
+ if( !ci->nonsegmented )
+ CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp,
+ &torso.oldframe, &torso.frame, &torso.backlerp );
+ else
+ CG_PlayerNonSegAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp );
+
+ // add the talk baloon or disconnect icon
+ CG_PlayerSprites( cent );
+
+ // add the shadow
+ if( ( es->number == cg.snap->ps.clientNum && cg.renderingThirdPerson ) ||
+ es->number != cg.snap->ps.clientNum )
+ shadow = CG_PlayerShadow( cent, &shadowPlane, class );
+
+ // add a water splash if partially in and out of water
+ CG_PlayerSplash( cent, class );
+
+ if( cg_shadows.integer == 3 && shadow )
+ renderfx |= RF_SHADOW_PLANE;
+
+ renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
+
+ //
+ // add the legs
+ //
+ if( !ci->nonsegmented )
+ {
+ legs.hModel = ci->legsModel;
+
+ if( held & ( 1 << UP_LIGHTARMOUR ) )
+ legs.customSkin = cgs.media.larmourLegsSkin;
+ else
+ legs.customSkin = ci->legsSkin;
+ }
+ else
+ {
+ legs.hModel = ci->nonSegModel;
+ legs.customSkin = ci->nonSegSkin;
+ }
+
+ VectorCopy( cent->lerpOrigin, legs.origin );
+
+ VectorCopy( cent->lerpOrigin, legs.lightingOrigin );
+ legs.shadowPlane = shadowPlane;
+ legs.renderfx = renderfx;
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+
+ //move the origin closer into the wall with a CapTrace
+ if( es->eFlags & EF_WALLCLIMB && !( es->eFlags & EF_DEAD ) && !( cg.intermissionStarted ) )
+ {
+ vec3_t start, end, mins, maxs;
+ trace_t tr;
+
+ if( es->eFlags & EF_WALLCLIMBCEILING )
+ VectorSet( surfNormal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( es->angles2, surfNormal );
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+
+ VectorMA( legs.origin, -TRACE_DEPTH, surfNormal, end );
+ VectorMA( legs.origin, 1.0f, surfNormal, start );
+ CG_CapTrace( &tr, start, mins, maxs, end, es->number, MASK_PLAYERSOLID );
+
+ //if the trace misses completely then just use legs.origin
+ //apparently capsule traces are "smaller" than box traces
+ if( tr.fraction != 1.0f )
+ VectorMA( legs.origin, tr.fraction * -TRACE_DEPTH, surfNormal, legs.origin );
+
+ VectorCopy( legs.origin, legs.lightingOrigin );
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+ }
+
+ //rescale the model
+ scale = BG_FindModelScaleForClass( class );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] );
+ VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] );
+ VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] );
+
+ legs.nonNormalizedAxes = qtrue;
+ }
+
+ //offset on the Z axis if required
+ VectorMA( legs.origin, BG_FindZOffsetForClass( class ), surfNormal, legs.origin );
+ VectorCopy( legs.origin, legs.lightingOrigin );
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+
+ trap_R_AddRefEntityToScene( &legs );
+
+ // if the model failed, allow the default nullmodel to be displayed
+ if( !legs.hModel )
+ return;
+
+ if( !ci->nonsegmented )
+ {
+ //
+ // add the torso
+ //
+ torso.hModel = ci->torsoModel;
+
+ if( held & ( 1 << UP_LIGHTARMOUR ) )
+ torso.customSkin = cgs.media.larmourTorsoSkin;
+ else
+ torso.customSkin = ci->torsoSkin;
+
+ if( !torso.hModel )
+ return;
+
+ VectorCopy( cent->lerpOrigin, torso.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" );
+
+ torso.shadowPlane = shadowPlane;
+ torso.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &torso );
+
+ //
+ // add the head
+ //
+ head.hModel = ci->headModel;
+
+ if( held & ( 1 << UP_HELMET ) )
+ head.customSkin = cgs.media.larmourHeadSkin;
+ else
+ head.customSkin = ci->headSkin;
+
+ if( !head.hModel )
+ return;
+
+ VectorCopy( cent->lerpOrigin, head.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head" );
+
+ head.shadowPlane = shadowPlane;
+ head.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &head );
+ }
+
+ //
+ // add the gun / barrel / flash
+ //
+ if( es->weapon != WP_NONE )
+ {
+ if( !ci->nonsegmented )
+ CG_AddPlayerWeapon( &torso, NULL, cent );
+ else
+ CG_AddPlayerWeapon( &legs, NULL, cent );
+ }
+
+ CG_PlayerUpgrades( cent, &torso );
+
+ //sanity check that particle systems are stopped when dead
+ if( es->eFlags & EF_DEAD )
+ {
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ CG_DestroyParticleSystem( &cent->muzzlePS );
+
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+ }
+
+ VectorCopy( surfNormal, cent->pe.lastNormal );
+}
+
+/*
+===============
+CG_Corpse
+===============
+*/
+void CG_Corpse( centity_t *cent )
+{
+ clientInfo_t *ci;
+ refEntity_t legs;
+ refEntity_t torso;
+ refEntity_t head;
+ entityState_t *es = &cent->currentState;
+ int corpseNum;
+ int renderfx;
+ qboolean shadow = qfalse;
+ float shadowPlane;
+ vec3_t origin, liveZ, deadZ;
+ float scale;
+
+ corpseNum = CG_GetCorpseNum( es->clientNum );
+
+ if( corpseNum < 0 || corpseNum >= MAX_CLIENTS )
+ CG_Error( "Bad corpseNum on corpse entity: %d", corpseNum );
+
+ ci = &cgs.corpseinfo[ corpseNum ];
+
+ // it is possible to see corpses from disconnected players that may
+ // not have valid clientinfo
+ if( !ci->infoValid )
+ return;
+
+ memset( &legs, 0, sizeof( legs ) );
+ memset( &torso, 0, sizeof( torso ) );
+ memset( &head, 0, sizeof( head ) );
+
+ VectorCopy( cent->lerpOrigin, origin );
+ BG_FindBBoxForClass( es->clientNum, liveZ, NULL, NULL, deadZ, NULL );
+ origin[ 2 ] -= ( liveZ[ 2 ] - deadZ[ 2 ] );
+
+ VectorCopy( es->angles, cent->lerpAngles );
+
+ // get the rotation information
+ if( !ci->nonsegmented )
+ CG_PlayerAngles( cent, cent->lerpAngles, legs.axis, torso.axis, head.axis );
+ else
+ CG_PlayerNonSegAngles( cent, cent->lerpAngles, legs.axis );
+
+ //set the correct frame (should always be dead)
+ if( cg_noPlayerAnims.integer )
+ legs.oldframe = legs.frame = torso.oldframe = torso.frame = 0;
+ else if( !ci->nonsegmented )
+ {
+ memset( &cent->pe.legs, 0, sizeof( lerpFrame_t ) );
+ CG_RunPlayerLerpFrame( ci, &cent->pe.legs, es->legsAnim, 1 );
+ legs.oldframe = cent->pe.legs.oldFrame;
+ legs.frame = cent->pe.legs.frame;
+ legs.backlerp = cent->pe.legs.backlerp;
+
+ memset( &cent->pe.torso, 0, sizeof( lerpFrame_t ) );
+ CG_RunPlayerLerpFrame( ci, &cent->pe.torso, es->torsoAnim, 1 );
+ torso.oldframe = cent->pe.torso.oldFrame;
+ torso.frame = cent->pe.torso.frame;
+ torso.backlerp = cent->pe.torso.backlerp;
+ }
+ else
+ {
+ memset( &cent->pe.nonseg, 0, sizeof( lerpFrame_t ) );
+ CG_RunPlayerLerpFrame( ci, &cent->pe.nonseg, es->legsAnim, 1 );
+ legs.oldframe = cent->pe.nonseg.oldFrame;
+ legs.frame = cent->pe.nonseg.frame;
+ legs.backlerp = cent->pe.nonseg.backlerp;
+ }
+
+ // add the shadow
+ shadow = CG_PlayerShadow( cent, &shadowPlane, es->clientNum );
+
+ // get the player model information
+ renderfx = 0;
+
+ if( cg_shadows.integer == 3 && shadow )
+ renderfx |= RF_SHADOW_PLANE;
+
+ renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
+
+ //
+ // add the legs
+ //
+ if( !ci->nonsegmented )
+ {
+ legs.hModel = ci->legsModel;
+ legs.customSkin = ci->legsSkin;
+ }
+ else
+ {
+ legs.hModel = ci->nonSegModel;
+ legs.customSkin = ci->nonSegSkin;
+ }
+
+ VectorCopy( origin, legs.origin );
+
+ VectorCopy( origin, legs.lightingOrigin );
+ legs.shadowPlane = shadowPlane;
+ legs.renderfx = renderfx;
+ legs.origin[ 2 ] += BG_FindZOffsetForClass( es->clientNum );
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+
+ //rescale the model
+ scale = BG_FindModelScaleForClass( es->clientNum );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] );
+ VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] );
+ VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] );
+
+ legs.nonNormalizedAxes = qtrue;
+ }
+
+ //CG_AddRefEntityWithPowerups( &legs, es->misc, ci->team );
+ trap_R_AddRefEntityToScene( &legs );
+
+ // if the model failed, allow the default nullmodel to be displayed
+ if( !legs.hModel )
+ return;
+
+ if( !ci->nonsegmented )
+ {
+ //
+ // add the torso
+ //
+ torso.hModel = ci->torsoModel;
+ if( !torso.hModel )
+ return;
+
+ torso.customSkin = ci->torsoSkin;
+
+ VectorCopy( origin, torso.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" );
+
+ torso.shadowPlane = shadowPlane;
+ torso.renderfx = renderfx;
+
+ //CG_AddRefEntityWithPowerups( &torso, es->misc, ci->team );
+ trap_R_AddRefEntityToScene( &torso );
+
+ //
+ // add the head
+ //
+ head.hModel = ci->headModel;
+ if( !head.hModel )
+ return;
+
+ head.customSkin = ci->headSkin;
+
+ VectorCopy( origin, head.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head");
+
+ head.shadowPlane = shadowPlane;
+ head.renderfx = renderfx;
+
+ //CG_AddRefEntityWithPowerups( &head, es->misc, ci->team );
+ trap_R_AddRefEntityToScene( &head );
+ }
+}
+
+
+//=====================================================================
+
+/*
+===============
+CG_ResetPlayerEntity
+
+A player just came into view or teleported, so reset all animation info
+===============
+*/
+void CG_ResetPlayerEntity( centity_t *cent )
+{
+ cent->errorTime = -99999; // guarantee no error decay added
+ cent->extrapolated = qfalse;
+
+ CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ],
+ &cent->pe.legs, cent->currentState.legsAnim );
+ CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ],
+ &cent->pe.torso, cent->currentState.torsoAnim );
+ CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ],
+ &cent->pe.nonseg, cent->currentState.legsAnim );
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, cent->lerpOrigin );
+ BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );
+
+ VectorCopy( cent->lerpOrigin, cent->rawOrigin );
+ VectorCopy( cent->lerpAngles, cent->rawAngles );
+
+ memset( &cent->pe.legs, 0, sizeof( cent->pe.legs ) );
+ cent->pe.legs.yawAngle = cent->rawAngles[ YAW ];
+ cent->pe.legs.yawing = qfalse;
+ cent->pe.legs.pitchAngle = 0;
+ cent->pe.legs.pitching = qfalse;
+
+ memset( &cent->pe.torso, 0, sizeof( cent->pe.legs ) );
+ cent->pe.torso.yawAngle = cent->rawAngles[ YAW ];
+ cent->pe.torso.yawing = qfalse;
+ cent->pe.torso.pitchAngle = cent->rawAngles[ PITCH ];
+ cent->pe.torso.pitching = qfalse;
+
+ memset( &cent->pe.nonseg, 0, sizeof( cent->pe.nonseg ) );
+ cent->pe.nonseg.yawAngle = cent->rawAngles[ YAW ];
+ cent->pe.nonseg.yawing = qfalse;
+ cent->pe.nonseg.pitchAngle = cent->rawAngles[ PITCH ];
+ cent->pe.nonseg.pitching = qfalse;
+
+ if( cg_debugPosition.integer )
+ CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );
+}
+
+/*
+==================
+CG_PlayerDisconnect
+
+Player disconnecting
+==================
+*/
+void CG_PlayerDisconnect( vec3_t org )
+{
+ particleSystem_t *ps;
+
+ trap_S_StartSound( org, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.disconnectSound );
+
+ ps = CG_SpawnNewParticleSystem( cgs.media.disconnectPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, org );
+ CG_AttachToPoint( &ps->attachment );
+ }
+}
+
+/*
+=================
+CG_Bleed
+
+This is the spurt of blood when a character gets hit
+=================
+*/
+void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum )
+{
+ pTeam_t team = cgs.clientinfo[ entityNum ].team;
+ qhandle_t bleedPS;
+ particleSystem_t *ps;
+
+ if( !cg_blood.integer )
+ return;
+
+ if( team == PTE_ALIENS )
+ bleedPS = cgs.media.alienBleedPS;
+ else if( team == PTE_HUMANS )
+ bleedPS = cgs.media.humanBleedPS;
+ else
+ return;
+
+ ps = CG_SpawnNewParticleSystem( bleedPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, origin );
+ CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] );
+ CG_AttachToPoint( &ps->attachment );
+
+ CG_SetParticleSystemNormal( ps, normal );
+ }
+}
+
+/*
+===============
+CG_AtHighestClass
+
+Is the local client at the highest class possible?
+===============
+*/
+qboolean CG_AtHighestClass( void )
+{
+ int i;
+ qboolean superiorClasses = qfalse;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ if( BG_ClassCanEvolveFromTo(
+ cg.predictedPlayerState.stats[ STAT_PCLASS ], i,
+ ALIEN_MAX_KILLS, 0 ) >= 0 &&
+ BG_FindStagesForClass( i, cgs.alienStage ) &&
+ BG_ClassIsAllowed( i ) )
+ {
+ superiorClasses = qtrue;
+ break;
+ }
+ }
+
+ return !superiorClasses;
+}
+
diff --git a/src/cgame/cg_playerstate.c b/src/cgame/cg_playerstate.c
new file mode 100644
index 0000000..e1bcb09
--- /dev/null
+++ b/src/cgame/cg_playerstate.c
@@ -0,0 +1,317 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_playerstate.c -- this file acts on changes in a new playerState_t
+// With normal play, this will be done after local prediction, but when
+// following another player or playing back a demo, it will be checked
+// when the snapshot transitions like all the other entities
+
+
+#include "cg_local.h"
+
+/*
+==============
+CG_DamageFeedback
+==============
+*/
+void CG_DamageFeedback( int yawByte, int pitchByte, int damage )
+{
+ float left, front, up;
+ float kick;
+ int health;
+ float scale;
+ vec3_t dir;
+ vec3_t angles;
+ float dist;
+ float yaw, pitch;
+
+ // show the attacking player's head and name in corner
+ cg.attackerTime = cg.time;
+
+ // the lower on health you are, the greater the view kick will be
+ health = cg.snap->ps.stats[STAT_HEALTH];
+
+ if( health < 40 )
+ scale = 1;
+ else
+ scale = 40.0 / health;
+
+ kick = damage * scale;
+
+ if( kick < 5 )
+ kick = 5;
+
+ if( kick > 10 )
+ kick = 10;
+
+ // if yaw and pitch are both 255, make the damage always centered (falling, etc)
+ if( yawByte == 255 && pitchByte == 255 )
+ {
+ cg.damageX = 0;
+ cg.damageY = 0;
+ cg.v_dmg_roll = 0;
+ cg.v_dmg_pitch = -kick;
+ }
+ else
+ {
+ // positional
+ pitch = pitchByte / 255.0 * 360;
+ yaw = yawByte / 255.0 * 360;
+
+ angles[ PITCH ] = pitch;
+ angles[ YAW ] = yaw;
+ angles[ ROLL ] = 0;
+
+ AngleVectors( angles, dir, NULL, NULL );
+ VectorSubtract( vec3_origin, dir, dir );
+
+ front = DotProduct( dir, cg.refdef.viewaxis[ 0 ] );
+ left = DotProduct( dir, cg.refdef.viewaxis[ 1 ] );
+ up = DotProduct( dir, cg.refdef.viewaxis[ 2 ] );
+
+ dir[ 0 ] = front;
+ dir[ 1 ] = left;
+ dir[ 2 ] = 0;
+ dist = VectorLength( dir );
+
+ if( dist < 0.1f )
+ dist = 0.1f;
+
+ cg.v_dmg_roll = kick * left;
+
+ cg.v_dmg_pitch = -kick * front;
+
+ if( front <= 0.1 )
+ front = 0.1f;
+
+ cg.damageX = -left / front;
+ cg.damageY = up / dist;
+ }
+
+ // clamp the position
+ if( cg.damageX > 1.0 )
+ cg.damageX = 1.0;
+
+ if( cg.damageX < - 1.0 )
+ cg.damageX = -1.0;
+
+ if( cg.damageY > 1.0 )
+ cg.damageY = 1.0;
+
+ if( cg.damageY < - 1.0 )
+ cg.damageY = -1.0;
+
+ // don't let the screen flashes vary as much
+ if( kick > 10 )
+ kick = 10;
+
+ cg.damageValue = kick;
+ cg.v_dmg_time = cg.time + DAMAGE_TIME;
+ cg.damageTime = cg.snap->serverTime;
+}
+
+
+
+
+/*
+================
+CG_Respawn
+
+A respawn happened this snapshot
+================
+*/
+void CG_Respawn( void )
+{
+ // no error decay on player movement
+ cg.thisFrameTeleport = qtrue;
+
+ // display weapons available
+ cg.weaponSelectTime = cg.time;
+
+ // select the weapon the server says we are using
+ cg.weaponSelect = cg.snap->ps.weapon;
+
+ CG_ResetPainBlend( );
+}
+
+/*
+==============
+CG_CheckPlayerstateEvents
+
+==============
+*/
+void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops )
+{
+ int i;
+ int event;
+ centity_t *cent;
+
+ if( ps->externalEvent && ps->externalEvent != ops->externalEvent )
+ {
+ cent = &cg_entities[ ps->clientNum ];
+ cent->currentState.event = ps->externalEvent;
+ cent->currentState.eventParm = ps->externalEventParm;
+ CG_EntityEvent( cent, cent->lerpOrigin );
+ }
+
+ cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ];
+
+ // go through the predictable events buffer
+ for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ )
+ {
+ // if we have a new predictable event
+ if( i >= ops->eventSequence ||
+ // or the server told us to play another event instead of a predicted event we already issued
+ // or something the server told us changed our prediction causing a different event
+ ( i > ops->eventSequence - MAX_PS_EVENTS && ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] !=
+ ops->events[ i & ( MAX_PS_EVENTS - 1 ) ] ) )
+ {
+ event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ];
+
+ cent->currentState.event = event;
+ cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ];
+ CG_EntityEvent( cent, cent->lerpOrigin );
+ cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event;
+
+ cg.eventSequence++;
+ }
+ }
+}
+
+
+/*
+==================
+CG_CheckChangedPredictableEvents
+==================
+*/
+void CG_CheckChangedPredictableEvents( playerState_t *ps )
+{
+ int i;
+ int event;
+ centity_t *cent;
+
+ cent = &cg.predictedPlayerEntity;
+
+ for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ )
+ {
+ //
+ if( i >= cg.eventSequence )
+ continue;
+
+ // if this event is not further back in than the maximum predictable events we remember
+ if( i > cg.eventSequence - MAX_PREDICTED_EVENTS )
+ {
+ // if the new playerstate event is different from a previously predicted one
+ if( ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] != cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] )
+ {
+ event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ];
+ cent->currentState.event = event;
+ cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ];
+ CG_EntityEvent( cent, cent->lerpOrigin );
+
+ cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event;
+
+ if( cg_showmiss.integer )
+ CG_Printf( "WARNING: changed predicted event\n" );
+ }
+ }
+ }
+}
+
+/*
+==================
+CG_CheckLocalSounds
+==================
+*/
+void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops )
+{
+ int reward;
+
+ // don't play the sounds if the player just changed teams
+ if( ps->persistant[ PERS_TEAM ] != ops->persistant[ PERS_TEAM ] )
+ return;
+
+ // health changes of more than -1 should make pain sounds
+ if( ps->stats[ STAT_HEALTH ] < ops->stats[ STAT_HEALTH ] - 1 )
+ {
+ if( ps->stats[ STAT_HEALTH ] > 0 )
+ CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[ STAT_HEALTH ] );
+ }
+
+
+ // if we are going into the intermission, don't start any voices
+ if( cg.intermissionStarted )
+ return;
+
+ // reward sounds
+ reward = qfalse;
+}
+
+
+/*
+===============
+CG_TransitionPlayerState
+
+===============
+*/
+void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops )
+{
+ // check for changing follow mode
+ if( ps->clientNum != ops->clientNum )
+ {
+ cg.thisFrameTeleport = qtrue;
+ // make sure we don't get any unwanted transition effects
+ *ops = *ps;
+
+ CG_ResetPainBlend( );
+ }
+
+ // damage events (player is getting wounded)
+ if( ps->damageEvent != ops->damageEvent && ps->damageCount )
+ CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount );
+
+ // respawning
+ if( ps->persistant[ PERS_SPAWN_COUNT ] != ops->persistant[ PERS_SPAWN_COUNT ] )
+ CG_Respawn( );
+
+ if( cg.mapRestart )
+ {
+ CG_Respawn( );
+ cg.mapRestart = qfalse;
+ }
+
+ if( cg.snap->ps.pm_type != PM_INTERMISSION &&
+ ps->persistant[ PERS_TEAM ] != TEAM_SPECTATOR )
+ CG_CheckLocalSounds( ps, ops );
+
+ // run events
+ CG_CheckPlayerstateEvents( ps, ops );
+
+ // smooth the ducking viewheight change
+ if( ps->viewheight != ops->viewheight )
+ {
+ cg.duckChange = ps->viewheight - ops->viewheight;
+ cg.duckTime = cg.time;
+ }
+}
+
diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c
new file mode 100644
index 0000000..03442ed
--- /dev/null
+++ b/src/cgame/cg_predict.c
@@ -0,0 +1,879 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_predict.c -- this file generates cg.predictedPlayerState by either
+// interpolating between snapshots from the server or locally predicting
+// ahead the client's movement.
+// It also handles local physics interaction, like fragments bouncing off walls
+
+
+#include "cg_local.h"
+
+static pmove_t cg_pmove;
+
+static int cg_numSolidEntities;
+static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT];
+static int cg_numTriggerEntities;
+static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT];
+
+/*
+====================
+CG_BuildSolidList
+
+When a new cg.snap has been set, this function builds a sublist
+of the entities that are actually solid, to make for more
+efficient collision detection
+====================
+*/
+void CG_BuildSolidList( void )
+{
+ int i;
+ centity_t *cent;
+ snapshot_t *snap;
+ entityState_t *ent;
+
+ cg_numSolidEntities = 0;
+ cg_numTriggerEntities = 0;
+
+ if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport )
+ snap = cg.nextSnap;
+ else
+ snap = cg.snap;
+
+ for( i = 0; i < snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ snap->entities[ i ].number ];
+ ent = &cent->currentState;
+
+ if( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER )
+ {
+ cg_triggerEntities[ cg_numTriggerEntities ] = cent;
+ cg_numTriggerEntities++;
+ continue;
+ }
+
+ if( cent->nextState.solid && ent->eType != ET_MISSILE )
+ {
+ cg_solidEntities[ cg_numSolidEntities ] = cent;
+ cg_numSolidEntities++;
+ continue;
+ }
+ }
+}
+
+/*
+====================
+CG_ClipMoveToEntities
+
+====================
+*/
+static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins,
+ const vec3_t maxs, const vec3_t end, int skipNumber,
+ int mask, trace_t *tr, traceType_t collisionType )
+{
+ int i, j, x, zd, zu;
+ trace_t trace;
+ entityState_t *ent;
+ clipHandle_t cmodel;
+ vec3_t bmins, bmaxs;
+ vec3_t origin, angles;
+ centity_t *cent;
+
+ //SUPAR HACK
+ //this causes a trace to collide with the local player
+ if( skipNumber == MAGIC_TRACE_HACK )
+ j = cg_numSolidEntities + 1;
+ else
+ j = cg_numSolidEntities;
+
+ for( i = 0; i < j; i++ )
+ {
+ if( i < cg_numSolidEntities )
+ cent = cg_solidEntities[ i ];
+ else
+ cent = &cg.predictedPlayerEntity;
+
+ ent = &cent->currentState;
+
+ if( ent->number == skipNumber )
+ continue;
+
+ if( ent->solid == SOLID_BMODEL )
+ {
+ // special value for bmodel
+ cmodel = trap_CM_InlineModel( ent->modelindex );
+ VectorCopy( cent->lerpAngles, angles );
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.physicsTime, origin );
+ }
+ else
+ {
+ // encoded bbox
+ x = ( ent->solid & 255 );
+ zd = ( ( ent->solid >> 8 ) & 255 );
+ zu = ( ( ent->solid >> 16 ) & 255 ) - 32;
+
+ bmins[ 0 ] = bmins[ 1 ] = -x;
+ bmaxs[ 0 ] = bmaxs[ 1 ] = x;
+ bmins[ 2 ] = -zd;
+ bmaxs[ 2 ] = zu;
+
+ if( i == cg_numSolidEntities )
+ BG_FindBBoxForClass( ( ent->misc >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL );
+
+ cmodel = trap_CM_TempBoxModel( bmins, bmaxs );
+ VectorCopy( vec3_origin, angles );
+ VectorCopy( cent->lerpOrigin, origin );
+ }
+
+
+ if( collisionType == TT_CAPSULE )
+ {
+ trap_CM_TransformedCapsuleTrace ( &trace, start, end,
+ mins, maxs, cmodel, mask, origin, angles );
+ }
+ else if( collisionType == TT_AABB )
+ {
+ trap_CM_TransformedBoxTrace ( &trace, start, end,
+ mins, maxs, cmodel, mask, origin, angles );
+ }
+ else if( collisionType == TT_BISPHERE )
+ {
+ trap_CM_TransformedBiSphereTrace( &trace, start, end,
+ mins[ 0 ], maxs[ 0 ], cmodel, mask, origin );
+ }
+
+ if( trace.allsolid || trace.fraction < tr->fraction )
+ {
+ trace.entityNum = ent->number;
+
+ if( tr->lateralFraction < trace.lateralFraction )
+ {
+ float oldLateralFraction = tr->lateralFraction;
+ *tr = trace;
+ tr->lateralFraction = oldLateralFraction;
+ }
+ else
+ *tr = trace;
+ }
+ else if( trace.startsolid )
+ tr->startsolid = qtrue;
+
+ if( tr->allsolid )
+ return;
+ }
+}
+
+/*
+================
+CG_Trace
+================
+*/
+void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end,
+ int skipNumber, int mask )
+{
+ trace_t t;
+
+ trap_CM_BoxTrace( &t, start, end, mins, maxs, 0, mask );
+ t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
+ // check all other solid models
+ CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_AABB );
+
+ *result = t;
+}
+
+/*
+================
+CG_CapTrace
+================
+*/
+void CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end,
+ int skipNumber, int mask )
+{
+ trace_t t;
+
+ trap_CM_CapsuleTrace( &t, start, end, mins, maxs, 0, mask );
+ t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
+ // check all other solid models
+ CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_CAPSULE );
+
+ *result = t;
+}
+
+/*
+================
+CG_BiSphereTrace
+================
+*/
+void CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end,
+ const float startRadius, const float endRadius, int skipNumber, int mask )
+{
+ trace_t t;
+ vec3_t mins, maxs;
+
+ mins[ 0 ] = startRadius;
+ maxs[ 0 ] = endRadius;
+
+ trap_CM_BiSphereTrace( &t, start, end, startRadius, endRadius, 0, mask );
+ t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
+ // check all other solid models
+ CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_BISPHERE );
+
+ *result = t;
+}
+
+/*
+================
+CG_PointContents
+================
+*/
+int CG_PointContents( const vec3_t point, int passEntityNum )
+{
+ int i;
+ entityState_t *ent;
+ centity_t *cent;
+ clipHandle_t cmodel;
+ int contents;
+
+ contents = trap_CM_PointContents (point, 0);
+
+ for( i = 0; i < cg_numSolidEntities; i++ )
+ {
+ cent = cg_solidEntities[ i ];
+
+ ent = &cent->currentState;
+
+ if( ent->number == passEntityNum )
+ continue;
+
+ if( ent->solid != SOLID_BMODEL ) // special value for bmodel
+ continue;
+
+ cmodel = trap_CM_InlineModel( ent->modelindex );
+
+ if( !cmodel )
+ continue;
+
+ contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles );
+ }
+
+ return contents;
+}
+
+
+/*
+========================
+CG_InterpolatePlayerState
+
+Generates cg.predictedPlayerState by interpolating between
+cg.snap->player_state and cg.nextFrame->player_state
+========================
+*/
+static void CG_InterpolatePlayerState( qboolean grabAngles )
+{
+ float f;
+ int i;
+ playerState_t *out;
+ snapshot_t *prev, *next;
+
+ out = &cg.predictedPlayerState;
+ prev = cg.snap;
+ next = cg.nextSnap;
+
+ *out = cg.snap->ps;
+
+ // if we are still allowing local input, short circuit the view angles
+ if( grabAngles )
+ {
+ usercmd_t cmd;
+ int cmdNum;
+
+ cmdNum = trap_GetCurrentCmdNumber( );
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ PM_UpdateViewAngles( out, &cmd );
+ }
+
+ // if the next frame is a teleport, we can't lerp to it
+ if( cg.nextFrameTeleport )
+ return;
+
+ if( !next || next->serverTime <= prev->serverTime )
+ return;
+
+ f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime );
+
+ i = next->ps.bobCycle;
+ if( i < prev->ps.bobCycle )
+ i += 256; // handle wraparound
+
+ out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle );
+
+ for( i = 0; i < 3; i++ )
+ {
+ out->origin[ i ] = prev->ps.origin[ i ] + f * ( next->ps.origin[ i ] - prev->ps.origin[ i ] );
+
+ if( !grabAngles )
+ out->viewangles[ i ] = LerpAngle( prev->ps.viewangles[ i ], next->ps.viewangles[ i ], f );
+
+ out->velocity[ i ] = prev->ps.velocity[ i ] +
+ f * (next->ps.velocity[ i ] - prev->ps.velocity[ i ] );
+ }
+}
+
+
+/*
+=========================
+CG_TouchTriggerPrediction
+
+Predict push triggers and items
+=========================
+*/
+static void CG_TouchTriggerPrediction( void )
+{
+ int i;
+ trace_t trace;
+ entityState_t *ent;
+ clipHandle_t cmodel;
+ centity_t *cent;
+ qboolean spectator;
+
+ // dead clients don't activate triggers
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR );
+
+ if( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator )
+ return;
+
+ for( i = 0; i < cg_numTriggerEntities; i++ )
+ {
+ cent = cg_triggerEntities[ i ];
+ ent = &cent->currentState;
+
+ if( ent->solid != SOLID_BMODEL )
+ continue;
+
+ cmodel = trap_CM_InlineModel( ent->modelindex );
+ if( !cmodel )
+ continue;
+
+ trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin,
+ cg_pmove.mins, cg_pmove.maxs, cmodel, -1 );
+
+ if( !trace.startsolid )
+ continue;
+
+ if( ent->eType == ET_TELEPORT_TRIGGER )
+ cg.hyperspace = qtrue;
+ }
+}
+
+static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps )
+{
+ vec3_t delta;
+ int i;
+
+ if( pps->pm_type != ps->pm_type ||
+ pps->pm_flags != ps->pm_flags ||
+ pps->pm_time != ps->pm_time )
+ {
+ return 1;
+ }
+
+ VectorSubtract( pps->origin, ps->origin, delta );
+ if( VectorLengthSquared( delta ) > 0.1f * 0.1f )
+ {
+ if( cg_showmiss.integer )
+ {
+ CG_Printf( "origin delta: %.2f ", VectorLength( delta ) );
+ }
+ return 2;
+ }
+
+ VectorSubtract( pps->velocity, ps->velocity, delta );
+ if( VectorLengthSquared( delta ) > 0.1f * 0.1f )
+ {
+ if( cg_showmiss.integer )
+ {
+ CG_Printf( "velocity delta: %.2f ", VectorLength( delta ) );
+ }
+ return 3;
+ }
+
+ if( pps->weaponTime != ps->weaponTime ||
+ pps->gravity != ps->gravity ||
+ pps->speed != ps->speed ||
+ pps->delta_angles[ 0 ] != ps->delta_angles[ 0 ] ||
+ pps->delta_angles[ 1 ] != ps->delta_angles[ 1 ] ||
+ pps->delta_angles[ 2 ] != ps->delta_angles[ 2 ] ||
+ pps->groundEntityNum != ps->groundEntityNum )
+ {
+ return 4;
+ }
+
+ if( pps->legsTimer != ps->legsTimer ||
+ pps->legsAnim != ps->legsAnim ||
+ pps->torsoTimer != ps->torsoTimer ||
+ pps->torsoAnim != ps->torsoAnim ||
+ pps->movementDir != ps->movementDir )
+ {
+ return 5;
+ }
+
+ VectorSubtract( pps->grapplePoint, ps->grapplePoint, delta );
+ if( VectorLengthSquared( delta ) > 0.1f * 0.1f )
+ return 6;
+
+ if( pps->eFlags != ps->eFlags )
+ return 7;
+
+ if( pps->eventSequence != ps->eventSequence )
+ return 8;
+
+ for( i = 0; i < MAX_PS_EVENTS; i++ )
+ {
+ if ( pps->events[ i ] != ps->events[ i ] ||
+ pps->eventParms[ i ] != ps->eventParms[ i ] )
+ {
+ return 9;
+ }
+ }
+
+ if( pps->externalEvent != ps->externalEvent ||
+ pps->externalEventParm != ps->externalEventParm ||
+ pps->externalEventTime != ps->externalEventTime )
+ {
+ return 10;
+ }
+
+ if( pps->clientNum != ps->clientNum ||
+ pps->weapon != ps->weapon ||
+ pps->weaponstate != ps->weaponstate )
+ {
+ return 11;
+ }
+
+ if( fabs( AngleDelta( ps->viewangles[ 0 ], pps->viewangles[ 0 ] ) ) > 1.0f ||
+ fabs( AngleDelta( ps->viewangles[ 1 ], pps->viewangles[ 1 ] ) ) > 1.0f ||
+ fabs( AngleDelta( ps->viewangles[ 2 ], pps->viewangles[ 2 ] ) ) > 1.0f )
+ {
+ return 12;
+ }
+
+ if( pps->viewheight != ps->viewheight )
+ return 13;
+
+ if( pps->damageEvent != ps->damageEvent ||
+ pps->damageYaw != ps->damageYaw ||
+ pps->damagePitch != ps->damagePitch ||
+ pps->damageCount != ps->damageCount )
+ {
+ return 14;
+ }
+
+ for( i = 0; i < MAX_STATS; i++ )
+ {
+ if( pps->stats[ i ] != ps->stats[ i ] )
+ return 15;
+ }
+
+ for( i = 0; i < MAX_PERSISTANT; i++ )
+ {
+ if( pps->persistant[ i ] != ps->persistant[ i ] )
+ return 16;
+ }
+
+ for( i = 0; i < MAX_WEAPONS; i++ )
+ {
+ // GH FIXME
+ if( pps->ammo != ps->ammo || pps->clips != ps->clips )
+ return 18;
+ }
+
+ if( pps->generic1 != ps->generic1 ||
+ pps->loopSound != ps->loopSound )
+ {
+ return 19;
+ }
+
+ return 0;
+}
+
+
+/*
+=================
+CG_PredictPlayerState
+
+Generates cg.predictedPlayerState for the current cg.time
+cg.predictedPlayerState is guaranteed to be valid after exiting.
+
+For demo playback, this will be an interpolation between two valid
+playerState_t.
+
+For normal gameplay, it will be the result of predicted usercmd_t on
+top of the most recent playerState_t received from the server.
+
+Each new snapshot will usually have one or more new usercmd over the last,
+but we simulate all unacknowledged commands each time, not just the new ones.
+This means that on an internet connection, quite a few pmoves may be issued
+each frame.
+
+OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t
+differs from the predicted one. Would require saving all intermediate
+playerState_t during prediction.
+
+We detect prediction errors and allow them to be decayed off over several frames
+to ease the jerk.
+=================
+*/
+void CG_PredictPlayerState( void )
+{
+ int cmdNum, current, i;
+ playerState_t oldPlayerState;
+ qboolean moved;
+ usercmd_t oldestCmd;
+ usercmd_t latestCmd;
+ int stateIndex = 0, predictCmd = 0;
+
+ cg.hyperspace = qfalse; // will be set if touching a trigger_teleport
+
+ // if this is the first frame we must guarantee
+ // predictedPlayerState is valid even if there is some
+ // other error condition
+ if( !cg.validPPS )
+ {
+ cg.validPPS = qtrue;
+ cg.predictedPlayerState = cg.snap->ps;
+ }
+
+
+ // demo playback just copies the moves
+ if( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) )
+ {
+ CG_InterpolatePlayerState( qfalse );
+ return;
+ }
+
+ // non-predicting local movement will grab the latest angles
+ if( cg_nopredict.integer || cg_synchronousClients.integer )
+ {
+ CG_InterpolatePlayerState( qtrue );
+ return;
+ }
+
+ // prepare for pmove
+ cg_pmove.ps = &cg.predictedPlayerState;
+ cg_pmove.pmext = &cg.pmext;
+ cg_pmove.trace = CG_Trace;
+ cg_pmove.pointcontents = CG_PointContents;
+ cg_pmove.debugLevel = cg_debugMove.integer;
+
+ if( cg_pmove.ps->pm_type == PM_DEAD )
+ cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
+ else
+ cg_pmove.tracemask = MASK_PLAYERSOLID;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies
+
+ cg_pmove.noFootsteps = 0;
+
+ // save the state before the pmove so we can detect transitions
+ oldPlayerState = cg.predictedPlayerState;
+
+ current = trap_GetCurrentCmdNumber( );
+
+ // if we don't have the commands right after the snapshot, we
+ // can't accurately predict a current position, so just freeze at
+ // the last good position we had
+ cmdNum = current - CMD_BACKUP + 1;
+ trap_GetUserCmd( cmdNum, &oldestCmd );
+
+ if( oldestCmd.serverTime > cg.snap->ps.commandTime &&
+ oldestCmd.serverTime < cg.time )
+ { // special check for map_restart
+ if( cg_showmiss.integer )
+ CG_Printf( "exceeded PACKET_BACKUP on commands\n" );
+
+ return;
+ }
+
+ // get the latest command so we can know which commands are from previous map_restarts
+ trap_GetUserCmd( current, &latestCmd );
+
+ // get the most recent information we have, even if
+ // the server time is beyond our current cg.time,
+ // because predicted player positions are going to
+ // be ahead of everything else anyway
+ if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport )
+ {
+ cg.predictedPlayerState = cg.nextSnap->ps;
+ cg.physicsTime = cg.nextSnap->serverTime;
+ }
+ else
+ {
+ cg.predictedPlayerState = cg.snap->ps;
+ cg.physicsTime = cg.snap->serverTime;
+ }
+
+ if( pmove_msec.integer < 8 )
+ trap_Cvar_Set( "pmove_msec", "8" );
+ else if( pmove_msec.integer > 33 )
+ trap_Cvar_Set( "pmove_msec", "33" );
+
+ cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer;
+ cg_pmove.pmove_msec = pmove_msec.integer;
+
+ // Like the comments described above, a player's state is entirely
+ // re-predicted from the last valid snapshot every client frame, which
+ // can be really, really, really slow. Every old command has to be
+ // run again. For every client frame that is *not* directly after a
+ // snapshot, this is unnecessary, since we have no new information.
+ // For those, we'll play back the predictions from the last frame and
+ // predict only the newest commands. Essentially, we'll be doing
+ // an incremental predict instead of a full predict.
+ //
+ // If we have a new snapshot, we can compare its player state's command
+ // time to the command times in the queue to find a match. If we find
+ // a matching state, and the predicted version has not deviated, we can
+ // use the predicted state as a base - and also do an incremental predict.
+ //
+ // With this method, we get incremental predicts on every client frame
+ // except a frame following a new snapshot in which there was a prediction
+ // error. This yeilds anywhere from a 15% to 40% performance increase,
+ // depending on how much of a bottleneck the CPU is.
+ if( cg_optimizePrediction.integer )
+ {
+ if( cg.nextFrameTeleport || cg.thisFrameTeleport )
+ {
+ // do a full predict
+ cg.lastPredictedCommand = 0;
+ cg.stateTail = cg.stateHead;
+ predictCmd = current - CMD_BACKUP + 1;
+ }
+ // cg.physicsTime is the current snapshot's serverTime if it's the same
+ // as the last one
+ else if( cg.physicsTime == cg.lastServerTime )
+ {
+ // we have no new information, so do an incremental predict
+ predictCmd = cg.lastPredictedCommand + 1;
+ }
+ else
+ {
+ // we have a new snapshot
+ int i;
+ int errorcode;
+ qboolean error = qtrue;
+
+ // loop through the saved states queue
+ for( i = cg.stateHead; i != cg.stateTail;
+ i = ( i + 1 ) % NUM_SAVED_STATES )
+ {
+ // if we find a predicted state whose commandTime matches the snapshot
+ // player state's commandTime
+ if( cg.savedPmoveStates[ i ].commandTime !=
+ cg.predictedPlayerState.commandTime )
+ {
+ continue;
+ }
+ // make sure the state differences are acceptable
+ errorcode = CG_IsUnacceptableError( &cg.predictedPlayerState,
+ &cg.savedPmoveStates[ i ] );
+
+ if( errorcode )
+ {
+ if( cg_showmiss.integer )
+ CG_Printf("errorcode %d at %d\n", errorcode, cg.time);
+ break;
+ }
+
+ // this one is almost exact, so we'll copy it in as the starting point
+ *cg_pmove.ps = cg.savedPmoveStates[ i ];
+ // advance the head
+ cg.stateHead = ( i + 1 ) % NUM_SAVED_STATES;
+
+ // set the next command to predict
+ predictCmd = cg.lastPredictedCommand + 1;
+
+ // a saved state matched, so flag it
+ error = qfalse;
+ break;
+ }
+
+ // if no saved states matched
+ if( error )
+ {
+ // do a full predict
+ cg.lastPredictedCommand = 0;
+ cg.stateTail = cg.stateHead;
+ predictCmd = current - CMD_BACKUP + 1;
+ }
+ }
+
+ // keep track of the server time of the last snapshot so we
+ // know when we're starting from a new one in future calls
+ cg.lastServerTime = cg.physicsTime;
+ stateIndex = cg.stateHead;
+ }
+
+ // run cmds
+ moved = qfalse;
+
+ for( cmdNum = current - CMD_BACKUP + 1; cmdNum <= current; cmdNum++ )
+ {
+ // get the command
+ trap_GetUserCmd( cmdNum, &cg_pmove.cmd );
+
+ if( cg_pmove.pmove_fixed )
+ PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd );
+
+ // don't do anything if the time is before the snapshot player time
+ if( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime )
+ continue;
+
+ // don't do anything if the command was from a previous map_restart
+ if( cg_pmove.cmd.serverTime > latestCmd.serverTime )
+ continue;
+
+ // check for a prediction error from last frame
+ // on a lan, this will often be the exact value
+ // from the snapshot, but on a wan we will have
+ // to predict several commands to get to the point
+ // we want to compare
+ if( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime )
+ {
+ vec3_t delta;
+ float len;
+
+ if( cg.thisFrameTeleport )
+ {
+ // a teleport will not cause an error decay
+ VectorClear( cg.predictedError );
+
+ if( cg_showmiss.integer )
+ CG_Printf( "PredictionTeleport\n" );
+
+ cg.thisFrameTeleport = qfalse;
+ }
+ else
+ {
+ vec3_t adjusted;
+ CG_AdjustPositionForMover( cg.predictedPlayerState.origin,
+ cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted );
+
+ if( cg_showmiss.integer )
+ {
+ if( !VectorCompare( oldPlayerState.origin, adjusted ) )
+ CG_Printf("prediction error\n");
+ }
+
+ VectorSubtract( oldPlayerState.origin, adjusted, delta );
+ len = VectorLength( delta );
+
+ if( len > 0.1 )
+ {
+ if( cg_showmiss.integer )
+ CG_Printf( "Prediction miss: %f\n", len );
+
+ if( cg_errorDecay.integer )
+ {
+ int t;
+ float f;
+
+ t = cg.time - cg.predictedErrorTime;
+ f = ( cg_errorDecay.value - t ) / cg_errorDecay.value;
+
+ if( f < 0 )
+ f = 0;
+
+ if( f > 0 && cg_showmiss.integer )
+ CG_Printf( "Double prediction decay: %f\n", f );
+
+ VectorScale( cg.predictedError, f, cg.predictedError );
+ }
+ else
+ VectorClear( cg.predictedError );
+
+ VectorAdd( delta, cg.predictedError, cg.predictedError );
+ cg.predictedErrorTime = cg.oldTime;
+ }
+ }
+ }
+
+ // don't predict gauntlet firing, which is only supposed to happen
+ // when it actually inflicts damage
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ cg_pmove.autoWeaponHit[ i ] = qfalse;
+
+ if( cg_pmove.pmove_fixed )
+ cg_pmove.cmd.serverTime = ( ( cg_pmove.cmd.serverTime + pmove_msec.integer - 1 ) /
+ pmove_msec.integer ) * pmove_msec.integer;
+
+ if( !cg_optimizePrediction.integer )
+ {
+ Pmove( &cg_pmove );
+ }
+ else if( cg_optimizePrediction.integer && ( cmdNum >= predictCmd ||
+ ( stateIndex + 1 ) % NUM_SAVED_STATES == cg.stateHead ) )
+ {
+ Pmove( &cg_pmove );
+ // record the last predicted command
+ cg.lastPredictedCommand = cmdNum;
+
+ // if we haven't run out of space in the saved states queue
+ if( ( stateIndex + 1 ) % NUM_SAVED_STATES != cg.stateHead )
+ {
+ // save the state for the false case ( of cmdNum >= predictCmd )
+ // in later calls to this function
+ cg.savedPmoveStates[ stateIndex ] = *cg_pmove.ps;
+ stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES;
+ cg.stateTail = stateIndex;
+ }
+ }
+ else
+ {
+ *cg_pmove.ps = cg.savedPmoveStates[ stateIndex ];
+ stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES;
+ }
+
+ moved = qtrue;
+
+ // add push trigger movement effects
+ CG_TouchTriggerPrediction( );
+
+ // check for predictable events that changed from previous predictions
+ //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState);
+ }
+
+ // adjust for the movement of the groundentity
+ CG_AdjustPositionForMover( cg.predictedPlayerState.origin,
+ cg.predictedPlayerState.groundEntityNum,
+ cg.physicsTime, cg.time, cg.predictedPlayerState.origin );
+
+
+ // fire events and other transition triggered things
+ CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState );
+
+
+}
diff --git a/src/cgame/cg_ptr.c b/src/cgame/cg_ptr.c
new file mode 100644
index 0000000..1881087
--- /dev/null
+++ b/src/cgame/cg_ptr.c
@@ -0,0 +1,81 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_ptr.c -- post timeout restoration handling
+
+
+#include "cg_local.h"
+
+#define PTRC_FILE "ptrc.cfg"
+
+/*
+===============
+CG_ReadPTRCode
+
+Read a PTR code from disk
+===============
+*/
+int CG_ReadPTRCode( void )
+{
+ int len;
+ char text[ 16 ];
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( PTRC_FILE, &f, FS_READ );
+ if( len <= 0 )
+ return 0;
+
+ // should never happen - malformed write
+ if( len >= sizeof( text ) - 1 )
+ return 0;
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ return atoi( text );
+}
+
+/*
+===============
+CG_WritePTRCode
+
+Write a PTR code to disk
+===============
+*/
+void CG_WritePTRCode( int code )
+{
+ char text[ 16 ];
+ fileHandle_t f;
+
+ Com_sprintf( text, 16, "%d", code );
+
+ // open file
+ if( trap_FS_FOpenFile( PTRC_FILE, &f, FS_WRITE ) < 0 )
+ return;
+
+ // write the code
+ trap_FS_Write( text, strlen( text ), f );
+
+ trap_FS_FCloseFile( f );
+}
diff --git a/src/cgame/cg_public.h b/src/cgame/cg_public.h
new file mode 100644
index 0000000..543a222
--- /dev/null
+++ b/src/cgame/cg_public.h
@@ -0,0 +1,273 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#define CMD_BACKUP 64
+#define CMD_MASK (CMD_BACKUP - 1)
+// allow a lot of command backups for very fast systems
+// multiple commands may be combined into a single packet, so this
+// needs to be larger than PACKET_BACKUP
+
+
+#define MAX_ENTITIES_IN_SNAPSHOT 256
+
+// snapshots are a view of the server at a given time
+
+// Snapshots are generated at regular time intervals by the server,
+// but they may not be sent if a client's rate level is exceeded, or
+// they may be dropped by the network.
+typedef struct
+{
+ int snapFlags; // SNAPFLAG_RATE_DELAYED, etc
+ int ping;
+
+ int serverTime; // server time the message is valid for (in msec)
+
+ byte areamask[ MAX_MAP_AREA_BYTES ]; // portalarea visibility bits
+
+ playerState_t ps; // complete information about the current player at this time
+
+ int numEntities; // all of the entities that need to be presented
+ entityState_t entities[ MAX_ENTITIES_IN_SNAPSHOT ]; // at the time of this snapshot
+
+ int numServerCommands; // text based server commands to execute when this
+ int serverCommandSequence; // snapshot becomes current
+} snapshot_t;
+
+enum
+{
+ CGAME_EVENT_NONE,
+ CGAME_EVENT_TEAMMENU,
+ CGAME_EVENT_SCOREBOARD,
+ CGAME_EVENT_EDITHUD
+};
+
+/*
+==================================================================
+
+functions imported from the main executable
+
+==================================================================
+*/
+
+#define CGAME_IMPORT_API_VERSION 4
+
+typedef enum
+{
+ CG_PRINT,
+ CG_ERROR,
+ CG_MILLISECONDS,
+ CG_CVAR_REGISTER,
+ CG_CVAR_UPDATE,
+ CG_CVAR_SET,
+ CG_CVAR_VARIABLESTRINGBUFFER,
+ CG_ARGC,
+ CG_ARGV,
+ CG_ARGS,
+ CG_FS_FOPENFILE,
+ CG_FS_READ,
+ CG_FS_WRITE,
+ CG_FS_FCLOSEFILE,
+ CG_SENDCONSOLECOMMAND,
+ CG_ADDCOMMAND,
+ CG_SENDCLIENTCOMMAND,
+ CG_UPDATESCREEN,
+ CG_CM_LOADMAP,
+ CG_CM_NUMINLINEMODELS,
+ CG_CM_INLINEMODEL,
+ CG_CM_LOADMODEL,
+ CG_CM_TEMPBOXMODEL,
+ CG_CM_POINTCONTENTS,
+ CG_CM_TRANSFORMEDPOINTCONTENTS,
+ CG_CM_BOXTRACE,
+ CG_CM_TRANSFORMEDBOXTRACE,
+ CG_CM_MARKFRAGMENTS,
+ CG_S_STARTSOUND,
+ CG_S_STARTLOCALSOUND,
+ CG_S_CLEARLOOPINGSOUNDS,
+ CG_S_ADDLOOPINGSOUND,
+ CG_S_UPDATEENTITYPOSITION,
+ CG_S_RESPATIALIZE,
+ CG_S_REGISTERSOUND,
+ CG_S_STARTBACKGROUNDTRACK,
+ CG_R_LOADWORLDMAP,
+ CG_R_REGISTERMODEL,
+ CG_R_REGISTERSKIN,
+ CG_R_REGISTERSHADER,
+ CG_R_CLEARSCENE,
+ CG_R_ADDREFENTITYTOSCENE,
+ CG_R_ADDPOLYTOSCENE,
+ CG_R_ADDLIGHTTOSCENE,
+ CG_R_RENDERSCENE,
+ CG_R_SETCOLOR,
+#ifndef MODULE_INTERFACE_11
+ CG_R_SETCLIPREGION,
+#endif
+ CG_R_DRAWSTRETCHPIC,
+ CG_R_MODELBOUNDS,
+ CG_R_LERPTAG,
+ CG_GETGLCONFIG,
+ CG_GETGAMESTATE,
+ CG_GETCURRENTSNAPSHOTNUMBER,
+ CG_GETSNAPSHOT,
+ CG_GETSERVERCOMMAND,
+ CG_GETCURRENTCMDNUMBER,
+ CG_GETUSERCMD,
+ CG_SETUSERCMDVALUE,
+ CG_R_REGISTERSHADERNOMIP,
+ CG_MEMORY_REMAINING,
+ CG_R_REGISTERFONT,
+ CG_KEY_ISDOWN,
+ CG_KEY_GETCATCHER,
+ CG_KEY_SETCATCHER,
+ CG_KEY_GETKEY,
+#ifdef MODULE_INTERFACE_11
+ CG_PARSE_ADD_GLOBAL_DEFINE,
+ CG_PARSE_LOAD_SOURCE,
+ CG_PARSE_FREE_SOURCE,
+ CG_PARSE_READ_TOKEN,
+ CG_PARSE_SOURCE_FILE_AND_LINE,
+#endif
+ CG_S_STOPBACKGROUNDTRACK,
+ CG_REAL_TIME,
+ CG_SNAPVECTOR,
+ CG_REMOVECOMMAND,
+ CG_R_LIGHTFORPOINT,
+ CG_CIN_PLAYCINEMATIC,
+ CG_CIN_STOPCINEMATIC,
+ CG_CIN_RUNCINEMATIC,
+ CG_CIN_DRAWCINEMATIC,
+ CG_CIN_SETEXTENTS,
+ CG_R_REMAP_SHADER,
+ CG_S_ADDREALLOOPINGSOUND,
+ CG_S_STOPLOOPINGSOUND,
+
+ CG_CM_TEMPCAPSULEMODEL,
+ CG_CM_CAPSULETRACE,
+ CG_CM_TRANSFORMEDCAPSULETRACE,
+ CG_R_ADDADDITIVELIGHTTOSCENE,
+ CG_GET_ENTITY_TOKEN,
+ CG_R_ADDPOLYSTOSCENE,
+ CG_R_INPVS,
+ CG_FS_SEEK,
+ CG_FS_GETFILELIST,
+ CG_LITERAL_ARGS,
+ CG_CM_BISPHERETRACE,
+ CG_CM_TRANSFORMEDBISPHERETRACE,
+ CG_GETDEMOSTATE,
+ CG_GETDEMOPOS,
+ CG_GETDEMONAME,
+
+ CG_KEY_KEYNUMTOSTRINGBUF,
+ CG_KEY_GETBINDINGBUF,
+ CG_KEY_SETBINDING,
+
+#ifndef MODULE_INTERFACE_11
+ CG_PARSE_ADD_GLOBAL_DEFINE,
+ CG_PARSE_LOAD_SOURCE,
+ CG_PARSE_FREE_SOURCE,
+ CG_PARSE_READ_TOKEN,
+ CG_PARSE_SOURCE_FILE_AND_LINE,
+
+ CG_KEY_SETOVERSTRIKEMODE,
+ CG_KEY_GETOVERSTRIKEMODE,
+
+ CG_S_SOUNDDURATION,
+#endif
+
+ CG_MEMSET = 200,
+ CG_MEMCPY,
+ CG_STRNCPY,
+ CG_SIN,
+ CG_COS,
+ CG_ATAN2,
+ CG_SQRT,
+ CG_FLOOR,
+ CG_CEIL,
+
+ CG_TESTPRINTINT,
+ CG_TESTPRINTFLOAT,
+ CG_ACOS
+} cgameImport_t;
+
+
+/*
+==================================================================
+
+functions exported to the main executable
+
+==================================================================
+*/
+
+typedef enum
+{
+ CG_INIT,
+ // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum )
+ // called when the level loads or when the renderer is restarted
+ // all media should be registered at this time
+ // cgame will display loading status by calling SCR_Update, which
+ // will call CG_DrawInformation during the loading process
+ // reliableCommandSequence will be 0 on fresh loads, but higher for
+ // demos, tourney restarts, or vid_restarts
+
+ CG_SHUTDOWN,
+ // void (*CG_Shutdown)( void );
+ // oportunity to flush and close any open files
+
+ CG_CONSOLE_COMMAND,
+ // qboolean (*CG_ConsoleCommand)( void );
+ // a console command has been issued locally that is not recognized by the
+ // main game system.
+ // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the
+ // command is not known to the game
+
+ CG_DRAW_ACTIVE_FRAME,
+ // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback );
+ // Generates and draws a game scene and status information at the given time.
+ // If demoPlayback is set, local movement prediction will not be enabled
+
+ CG_CROSSHAIR_PLAYER,
+ // int (*CG_CrosshairPlayer)( void );
+
+ CG_LAST_ATTACKER,
+ // int (*CG_LastAttacker)( void );
+
+ CG_KEY_EVENT,
+ // void (*CG_KeyEvent)( int key, qboolean down );
+
+ CG_MOUSE_EVENT,
+ // void (*CG_MouseEvent)( int dx, int dy );
+ CG_EVENT_HANDLING,
+ // void (*CG_EventHandling)(int type);
+
+ CG_CONSOLE_TEXT,
+ // void (*CG_ConsoleText)( void );
+ // pass text that has been printed to the console to cgame
+ // use Cmd_Argc() / Cmd_Argv() to read it
+
+ CG_VOIP_STRING
+ // char *(*CG_VoIPString)( void );
+ // returns a string of comma-delimited clientnums based on cl_voipSendTarget
+} cgameExport_t;
+
+//----------------------------------------------
diff --git a/src/cgame/cg_scanner.c b/src/cgame/cg_scanner.c
new file mode 100644
index 0000000..033960e
--- /dev/null
+++ b/src/cgame/cg_scanner.c
@@ -0,0 +1,365 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#include "cg_local.h"
+
+static entityPos_t entityPositions;
+
+#define HUMAN_SCANNER_UPDATE_PERIOD 700
+
+/*
+=============
+CG_UpdateEntityPositions
+
+Update this client's perception of entity positions
+=============
+*/
+void CG_UpdateEntityPositions( void )
+{
+ centity_t *cent = NULL;
+ int i;
+
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( entityPositions.lastUpdateTime + HUMAN_SCANNER_UPDATE_PERIOD > cg.time )
+ return;
+ }
+
+ VectorCopy( cg.refdef.vieworg, entityPositions.origin );
+ VectorCopy( cg.refdefViewAngles, entityPositions.vangles );
+ entityPositions.lastUpdateTime = cg.time;
+
+ entityPositions.numAlienBuildables = 0;
+ entityPositions.numHumanBuildables = 0;
+ entityPositions.numAlienClients = 0;
+ entityPositions.numHumanClients = 0;
+
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+
+ if( cent->currentState.eType == ET_BUILDABLE )
+ {
+ //TA: add to list of item positions (for creep)
+ if( cent->currentState.modelindex2 == BIT_ALIENS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.alienBuildablePos[
+ entityPositions.numAlienBuildables ] );
+ entityPositions.alienBuildableTimes[
+ entityPositions.numAlienBuildables ] = cent->miscTime;
+
+ if( entityPositions.numAlienBuildables < MAX_GENTITIES )
+ entityPositions.numAlienBuildables++;
+ }
+ else if( cent->currentState.modelindex2 == BIT_HUMANS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.humanBuildablePos[
+ entityPositions.numHumanBuildables ] );
+
+ if( entityPositions.numHumanBuildables < MAX_GENTITIES )
+ entityPositions.numHumanBuildables++;
+ }
+ }
+ else if( cent->currentState.eType == ET_PLAYER )
+ {
+ int team = cent->currentState.misc & 0x00FF;
+
+ if( team == PTE_ALIENS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.alienClientPos[
+ entityPositions.numAlienClients ] );
+
+ if( entityPositions.numAlienClients < MAX_CLIENTS )
+ entityPositions.numAlienClients++;
+ }
+ else if( team == PTE_HUMANS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.humanClientPos[
+ entityPositions.numHumanClients ] );
+
+ if( entityPositions.numHumanClients < MAX_CLIENTS )
+ entityPositions.numHumanClients++;
+ }
+ }
+ }
+}
+
+#define STALKWIDTH 2.0f
+#define BLIPX 16.0f
+#define BLIPY 8.0f
+#define FAR_ALPHA 0.8f
+#define NEAR_ALPHA 1.2f
+
+/*
+=============
+CG_DrawBlips
+
+Draw blips and stalks for the human scanner
+=============
+*/
+static void CG_DrawBlips( rectDef_t *rect, vec3_t origin, vec4_t colour )
+{
+ vec3_t drawOrigin;
+ vec3_t up = { 0, 0, 1 };
+ float alphaMod = 1.0f;
+ float timeFractionSinceRefresh = 1.0f -
+ ( (float)( cg.time - entityPositions.lastUpdateTime ) /
+ (float)HUMAN_SCANNER_UPDATE_PERIOD );
+ vec4_t localColour;
+
+ Vector4Copy( colour, localColour );
+
+ RotatePointAroundVector( drawOrigin, up, origin, -entityPositions.vangles[ 1 ] - 90 );
+ drawOrigin[ 0 ] /= ( 2 * HELMET_RANGE / rect->w );
+ drawOrigin[ 1 ] /= ( 2 * HELMET_RANGE / rect->h );
+ drawOrigin[ 2 ] /= ( 2 * HELMET_RANGE / rect->w );
+
+ alphaMod = FAR_ALPHA +
+ ( ( drawOrigin[ 1 ] + ( rect->h / 2.0f ) ) / rect->h ) * ( NEAR_ALPHA - FAR_ALPHA );
+
+ localColour[ 3 ] *= alphaMod;
+ localColour[ 3 ] *= ( 0.5f + ( timeFractionSinceRefresh * 0.5f ) );
+
+ if( localColour[ 3 ] > 1.0f )
+ localColour[ 3 ] = 1.0f;
+ else if( localColour[ 3 ] < 0.0f )
+ localColour[ 3 ] = 0.0f;
+
+ trap_R_SetColor( localColour );
+
+ if( drawOrigin[ 2 ] > 0 )
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ],
+ rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ],
+ STALKWIDTH, drawOrigin[ 2 ], cgs.media.scannerLineShader );
+ else
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ],
+ rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ],
+ STALKWIDTH, -drawOrigin[ 2 ], cgs.media.scannerLineShader );
+
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX / 2 ) - drawOrigin[ 0 ],
+ rect->y + ( rect->h / 2 ) - ( BLIPY / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ],
+ BLIPX, BLIPY, cgs.media.scannerBlipShader );
+ trap_R_SetColor( NULL );
+}
+
+#define BLIPX2 24.0f
+#define BLIPY2 24.0f
+
+/*
+=============
+CG_DrawDir
+
+Draw dot marking the direction to an enemy
+=============
+*/
+static void CG_DrawDir( rectDef_t *rect, vec3_t origin, vec4_t colour )
+{
+ vec3_t drawOrigin;
+ vec3_t noZOrigin;
+ vec3_t normal, antinormal, normalDiff;
+ vec3_t view, noZview;
+ vec3_t up = { 0.0f, 0.0f, 1.0f };
+ vec3_t top = { 0.0f, -1.0f, 0.0f };
+ float angle;
+ playerState_t *ps = &cg.snap->ps;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( ps->grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+ AngleVectors( entityPositions.vangles, view, NULL, NULL );
+
+ ProjectPointOnPlane( noZOrigin, origin, normal );
+ ProjectPointOnPlane( noZview, view, normal );
+ VectorNormalize( noZOrigin );
+ VectorNormalize( noZview );
+
+ //calculate the angle between the images of the blip and the view
+ angle = RAD2DEG( acos( DotProduct( noZOrigin, noZview ) ) );
+ CrossProduct( noZOrigin, noZview, antinormal );
+ VectorNormalize( antinormal );
+
+ //decide which way to rotate
+ VectorSubtract( normal, antinormal, normalDiff );
+ if( VectorLength( normalDiff ) < 1.0f )
+ angle = 360.0f - angle;
+
+ RotatePointAroundVector( drawOrigin, up, top, angle );
+
+ trap_R_SetColor( colour );
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX2 / 2 ) - drawOrigin[ 0 ] * ( rect->w / 2 ),
+ rect->y + ( rect->h / 2 ) - ( BLIPY2 / 2 ) + drawOrigin[ 1 ] * ( rect->h / 2 ),
+ BLIPX2, BLIPY2, cgs.media.scannerBlipShader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+=============
+CG_AlienSense
+=============
+*/
+void CG_AlienSense( rectDef_t *rect )
+{
+ int i;
+ vec3_t origin;
+ vec3_t relOrigin;
+ vec4_t buildable = { 1.0f, 0.0f, 0.0f, 0.7f };
+ vec4_t client = { 0.0f, 0.0f, 1.0f, 0.7f };
+
+ VectorCopy( entityPositions.origin, origin );
+
+ //draw human buildables
+ for( i = 0; i < entityPositions.numHumanBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < ALIENSENSE_RANGE )
+ CG_DrawDir( rect, relOrigin, buildable );
+ }
+
+ //draw human clients
+ for( i = 0; i < entityPositions.numHumanClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < ALIENSENSE_RANGE )
+ CG_DrawDir( rect, relOrigin, client );
+ }
+}
+
+/*
+=============
+CG_Scanner
+=============
+*/
+void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color )
+{
+ int i;
+ vec3_t origin;
+ vec3_t relOrigin;
+ vec4_t hIabove;
+ vec4_t hIbelow;
+ vec4_t aIabove = { 1.0f, 0.0f, 0.0f, 0.75f };
+ vec4_t aIbelow = { 1.0f, 0.0f, 0.0f, 0.5f };
+
+ Vector4Copy( color, hIabove );
+ hIabove[ 3 ] *= 1.5f;
+ Vector4Copy( color, hIbelow );
+
+ VectorCopy( entityPositions.origin, origin );
+
+ //draw human buildables below scanner plane
+ for( i = 0; i < entityPositions.numHumanBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIbelow );
+ }
+
+ //draw alien buildables below scanner plane
+ for( i = 0; i < entityPositions.numAlienBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIbelow );
+ }
+
+ //draw human clients below scanner plane
+ for( i = 0; i < entityPositions.numHumanClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIbelow );
+ }
+
+ //draw alien buildables below scanner plane
+ for( i = 0; i < entityPositions.numAlienClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIbelow );
+ }
+
+ if( !cg_disableScannerPlane.integer )
+ {
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+ }
+
+ //draw human buildables above scanner plane
+ for( i = 0; i < entityPositions.numHumanBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIabove );
+ }
+
+ //draw alien buildables above scanner plane
+ for( i = 0; i < entityPositions.numAlienBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIabove );
+ }
+
+ //draw human clients above scanner plane
+ for( i = 0; i < entityPositions.numHumanClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIabove );
+ }
+
+ //draw alien clients above scanner plane
+ for( i = 0; i < entityPositions.numAlienClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIabove );
+ }
+}
diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c
new file mode 100644
index 0000000..7fb3e06
--- /dev/null
+++ b/src/cgame/cg_servercmds.c
@@ -0,0 +1,1016 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_servercmds.c -- reliably sequenced text commands sent by the server
+// these are processed at snapshot transition time, so there will definately
+// be a valid snapshot this frame
+
+
+#include "cg_local.h"
+
+/*
+=================
+CG_ParseScores
+
+=================
+*/
+static void CG_ParseScores( void )
+{
+ int i;
+
+ cg.numScores = atoi( CG_Argv( 1 ) );
+
+ if( cg.numScores > MAX_CLIENTS )
+ cg.numScores = MAX_CLIENTS;
+
+ cg.teamScores[ 0 ] = atoi( CG_Argv( 2 ) );
+ cg.teamScores[ 1 ] = atoi( CG_Argv( 3 ) );
+
+ memset( cg.scores, 0, sizeof( cg.scores ) );
+
+ if( cg_debugRandom.integer )
+ CG_Printf( "cg.numScores: %d\n", cg.numScores );
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ //
+ cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 4 ) );
+ cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 5 ) );
+ cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 6 ) );
+ cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 7 ) );
+ cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 8 ) );
+ cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 9 ) );
+
+ if( cg.scores[ i ].client < 0 || cg.scores[ i ].client >= MAX_CLIENTS )
+ cg.scores[ i ].client = 0;
+
+ cgs.clientinfo[ cg.scores[ i ].client ].score = cg.scores[ i ].score;
+ cgs.clientinfo[ cg.scores[ i ].client ].powerups = 0;
+
+ cg.scores[ i ].team = cgs.clientinfo[ cg.scores[ i ].client ].team;
+ }
+}
+
+/*
+=================
+CG_ParseTeamInfo
+
+=================
+*/
+static void CG_ParseTeamInfo( void )
+{
+ int i;
+ int client;
+
+ numSortedTeamPlayers = atoi( CG_Argv( 1 ) );
+
+ for( i = 0; i < numSortedTeamPlayers; i++ )
+ {
+ client = atoi( CG_Argv( i * 6 + 2 ) );
+
+ sortedTeamPlayers[ i ] = client;
+
+ cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) );
+ cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) );
+ cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) );
+ cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) );
+ cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) );
+ }
+}
+
+
+/*
+================
+CG_ParseServerinfo
+
+This is called explicitly when the gamestate is first received,
+and whenever the server updates any serverinfo flagged cvars
+================
+*/
+void CG_ParseServerinfo( void )
+{
+ const char *info;
+ char *mapname;
+
+ info = CG_ConfigString( CS_SERVERINFO );
+ cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) );
+ cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
+ cgs.markDeconstruct = atoi( Info_ValueForKey( info, "g_markDeconstruct" ) );
+ mapname = Info_ValueForKey( info, "mapname" );
+ Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
+}
+
+/*
+==================
+CG_ParseWarmup
+==================
+*/
+static void CG_ParseWarmup( void )
+{
+ const char *info;
+ int warmup;
+
+ info = CG_ConfigString( CS_WARMUP );
+
+ warmup = atoi( info );
+ cg.warmupCount = -1;
+
+ if( warmup == 0 && cg.warmup )
+ {
+ }
+
+ cg.warmup = warmup;
+}
+
+/*
+================
+CG_SetConfigValues
+
+Called on load to set the initial values from configure strings
+================
+*/
+void CG_SetConfigValues( void )
+{
+ sscanf( CG_ConfigString( CS_BUILDPOINTS ),
+ "%d %d %d %d %d", &cgs.alienBuildPoints,
+ &cgs.alienBuildPointsTotal,
+ &cgs.humanBuildPoints,
+ &cgs.humanBuildPointsTotal,
+ &cgs.humanBuildPointsPowered );
+
+ sscanf( CG_ConfigString( CS_STAGES ), "%d %d %d %d %d %d", &cgs.alienStage, &cgs.humanStage,
+ &cgs.alienKills, &cgs.humanKills, &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
+ sscanf( CG_ConfigString( CS_SPAWNS ), "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
+
+ cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
+ cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
+}
+
+
+/*
+=====================
+CG_ShaderStateChanged
+=====================
+*/
+void CG_ShaderStateChanged( void )
+{
+ char originalShader[ MAX_QPATH ];
+ char newShader[ MAX_QPATH ];
+ char timeOffset[ 16 ];
+ const char *o;
+ char *n, *t;
+
+ o = CG_ConfigString( CS_SHADERSTATE );
+
+ while( o && *o )
+ {
+ n = strstr( o, "=" );
+
+ if( n && *n )
+ {
+ strncpy( originalShader, o, n - o );
+ originalShader[ n - o ] = 0;
+ n++;
+ t = strstr( n, ":" );
+
+ if( t && *t )
+ {
+ strncpy( newShader, n, t - n );
+ newShader[ t - n ] = 0;
+ }
+ else
+ break;
+
+ t++;
+ o = strstr( t, "@" );
+
+ if( o )
+ {
+ strncpy( timeOffset, t, o - t );
+ timeOffset[ o - t ] = 0;
+ o++;
+ trap_R_RemapShader( originalShader, newShader, timeOffset );
+ }
+ }
+ else
+ break;
+ }
+}
+
+/*
+================
+CG_AnnounceAlienStageTransistion
+================
+*/
+static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to )
+{
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return;
+
+ trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER );
+ CG_CenterPrint( "We have evolved!", 200, GIANTCHAR_WIDTH * 4 );
+}
+
+/*
+================
+CG_AnnounceHumanStageTransistion
+================
+*/
+static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to )
+{
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER );
+ CG_CenterPrint( "Reinforcements have arrived!", 200, GIANTCHAR_WIDTH * 4 );
+}
+
+/*
+================
+CG_ConfigStringModified
+
+================
+*/
+static void CG_ConfigStringModified( void )
+{
+ const char *str;
+ int num;
+
+ num = atoi( CG_Argv( 1 ) );
+
+ // get the gamestate from the client system, which will have the
+ // new configstring already integrated
+ trap_GetGameState( &cgs.gameState );
+
+ // look up the individual string that was modified
+ str = CG_ConfigString( num );
+
+ // do something with it if necessary
+ if( num == CS_MUSIC )
+ CG_StartMusic( );
+ else if( num == CS_SERVERINFO )
+ CG_ParseServerinfo( );
+ else if( num == CS_WARMUP )
+ CG_ParseWarmup( );
+ else if( num == CS_BUILDPOINTS )
+ sscanf( str, "%d %d %d %d %d", &cgs.alienBuildPoints,
+ &cgs.alienBuildPointsTotal,
+ &cgs.humanBuildPoints,
+ &cgs.humanBuildPointsTotal,
+ &cgs.humanBuildPointsPowered );
+ else if( num == CS_STAGES )
+ {
+ stage_t oldAlienStage = cgs.alienStage;
+ stage_t oldHumanStage = cgs.humanStage;
+
+ sscanf( str, "%d %d %d %d %d %d",
+ &cgs.alienStage, &cgs.humanStage,
+ &cgs.alienKills, &cgs.humanKills,
+ &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
+
+ if( cgs.alienStage != oldAlienStage )
+ CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage );
+
+ if( cgs.humanStage != oldHumanStage )
+ CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage );
+ }
+ else if( num == CS_SPAWNS )
+ sscanf( str, "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
+ else if( num == CS_LEVEL_START_TIME )
+ cgs.levelStartTime = atoi( str );
+ else if( num == CS_VOTE_TIME )
+ {
+ cgs.voteTime = atoi( str );
+ cgs.voteModified = qtrue;
+
+ if( cgs.voteTime )
+ trap_Cvar_Set( "ui_voteActive", "1" );
+ else
+ trap_Cvar_Set( "ui_voteActive", "0" );
+ }
+ else if( num == CS_VOTE_YES )
+ {
+ cgs.voteYes = atoi( str );
+ cgs.voteModified = qtrue;
+ }
+ else if( num == CS_VOTE_NO )
+ {
+ cgs.voteNo = atoi( str );
+ cgs.voteModified = qtrue;
+ }
+ else if( num == CS_VOTE_STRING )
+ Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) );
+ else if( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1 )
+ {
+ int cs_offset = num - CS_TEAMVOTE_TIME;
+
+ cgs.teamVoteTime[ cs_offset ] = atoi( str );
+ cgs.teamVoteModified[ cs_offset ] = qtrue;
+
+ if( cs_offset == 0 )
+ {
+ if( cgs.teamVoteTime[ cs_offset ] )
+ trap_Cvar_Set( "ui_humanTeamVoteActive", "1" );
+ else
+ trap_Cvar_Set( "ui_humanTeamVoteActive", "0" );
+ }
+ else if( cs_offset == 1 )
+ {
+ if( cgs.teamVoteTime[ cs_offset ] )
+ trap_Cvar_Set( "ui_alienTeamVoteActive", "1" );
+ else
+ trap_Cvar_Set( "ui_alienTeamVoteActive", "0" );
+ }
+ }
+ else if( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 )
+ {
+ cgs.teamVoteYes[ num - CS_TEAMVOTE_YES ] = atoi( str );
+ cgs.teamVoteModified[ num - CS_TEAMVOTE_YES ] = qtrue;
+ }
+ else if( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 )
+ {
+ cgs.teamVoteNo[ num - CS_TEAMVOTE_NO ] = atoi( str );
+ cgs.teamVoteModified[ num - CS_TEAMVOTE_NO ] = qtrue;
+ }
+ else if( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 )
+ {
+ Q_strncpyz( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ], str,
+ sizeof( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ] ) );
+ }
+ else if( num == CS_INTERMISSION )
+ cg.intermissionStarted = atoi( str );
+ else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS )
+ cgs.gameModels[ num - CS_MODELS ] = trap_R_RegisterModel( str );
+ else if( num >= CS_SHADERS && num < CS_SHADERS+MAX_GAME_SHADERS )
+ cgs.gameShaders[ num - CS_SHADERS ] = trap_R_RegisterShader( str );
+ else if( num >= CS_PARTICLE_SYSTEMS && num < CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS )
+ cgs.gameParticleSystems[ num - CS_PARTICLE_SYSTEMS ] = CG_RegisterParticleSystem( (char *)str );
+ else if( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS )
+ {
+ if( str[ 0 ] != '*' )
+ { // player specific sounds don't register here
+ cgs.gameSounds[ num - CS_SOUNDS ] = trap_S_RegisterSound( str, qfalse );
+ }
+ }
+ else if( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS )
+ {
+ CG_NewClientInfo( num - CS_PLAYERS );
+ CG_BuildSpectatorString( );
+ }
+ else if( num == CS_WINNER )
+ {
+ trap_Cvar_Set( "ui_winner", str );
+ }
+ else if( num == CS_SHADERSTATE )
+ {
+ CG_ShaderStateChanged( );
+ }
+}
+
+
+/*
+===============
+CG_MapRestart
+
+The server has issued a map_restart, so the next snapshot
+is completely new and should not be interpolated to.
+
+A tournement restart will clear everything, but doesn't
+require a reload of all the media
+===============
+*/
+static void CG_MapRestart( void )
+{
+ if( cg_showmiss.integer )
+ CG_Printf( "CG_MapRestart\n" );
+
+ CG_InitMarkPolys( );
+
+ // make sure the "3 frags left" warnings play again
+ cg.fraglimitWarnings = 0;
+
+ cg.timelimitWarnings = 0;
+
+ cg.intermissionStarted = qfalse;
+
+ cgs.voteTime = 0;
+
+ cg.mapRestart = qtrue;
+
+ CG_StartMusic( );
+
+ trap_S_ClearLoopingSounds( qtrue );
+
+ // we really should clear more parts of cg here and stop sounds
+
+ // play the "fight" sound if this is a restart without warmup
+ if( cg.warmup == 0 )
+ CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 );
+
+ trap_Cvar_Set( "cg_thirdPerson", "0" );
+}
+
+/*
+=================
+CG_RemoveChatEscapeChar
+=================
+*/
+static void CG_RemoveChatEscapeChar( char *text )
+{
+ int i, l;
+
+ l = 0;
+ for( i = 0; text[ i ]; i++ )
+ {
+ if( text[ i ] == '\x19' )
+ continue;
+
+ text[ l++ ] = text[ i ];
+ }
+
+ text[ l ] = '\0';
+}
+
+/*
+===============
+CG_SetUIVars
+
+Set some cvars used by the UI
+===============
+*/
+static void CG_SetUIVars( void )
+{
+ int i;
+ char carriageCvar[ MAX_TOKEN_CHARS ];
+
+ *carriageCvar = 0;
+
+ //determine what the player is carrying
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) &&
+ BG_FindPurchasableForWeapon( i ) )
+ strcat( carriageCvar, va( "W%d ", i ) );
+ }
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) &&
+ BG_FindPurchasableForUpgrade( i ) )
+ strcat( carriageCvar, va( "U%d ", i ) );
+ }
+ strcat( carriageCvar, "$" );
+
+ trap_Cvar_Set( "ui_carriage", carriageCvar );
+
+ trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) );
+}
+
+
+/*
+==============
+CG_Menu
+==============
+*/
+void CG_Menu( int menu )
+{
+ const char *cmd = NULL; // command to send
+ const char *longMsg = NULL; // command parameter
+ const char *shortMsg = NULL; // non-modal version of message
+ CG_SetUIVars( );
+
+ // string literals have static storage duration, this is safe,
+ // cleaner and much more readable.
+ switch( menu )
+ {
+ case MN_TEAM:
+ cmd = "menu tremulous_teamselect\n";
+ break;
+
+ case MN_A_CLASS:
+ cmd = "menu tremulous_alienclass\n";
+ break;
+
+ case MN_H_SPAWN:
+ cmd = "menu tremulous_humanitem\n";
+ break;
+
+ case MN_A_BUILD:
+ cmd = "menu tremulous_alienbuild\n";
+ break;
+
+ case MN_H_BUILD:
+ cmd = "menu tremulous_humanbuild\n";
+ break;
+
+ case MN_H_ARMOURY:
+ cmd = "menu tremulous_humanarmoury\n";
+ break;
+
+ case MN_A_TEAMFULL:
+ longMsg = "The alien team has too many players. Please wait until slots "
+ "become available or join the human team.";
+ shortMsg = "The alien team has too many players\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_H_TEAMFULL:
+ longMsg = "The human team has too many players. Please wait until slots "
+ "become available or join the alien team.";
+ shortMsg = "The human team has too many players\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_A_TEAMCHANGEBUILDTIMER:
+ longMsg = "You cannot leave the Alien team until your build timer "
+ "has expired.";
+ shortMsg = "You cannot change teams until your build timer expires.\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_H_TEAMCHANGEBUILDTIMER:
+ longMsg = "You cannot leave the Human team until your build timer "
+ "has expired.";
+ shortMsg = "You cannot change teams until your build timer expires.\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ //===============================
+
+ case MN_H_NOROOM:
+ longMsg = "There is no room to build here. Move until the buildable turns "
+ "translucent green indicating a valid build location.";
+ shortMsg = "There is no room to build here\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOPOWER:
+ longMsg = "There is no power remaining. Free up power by destroying "
+ "existing buildable objects.";
+ shortMsg = "There is no power remaining\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOTPOWERED:
+ longMsg = "This buildable is not powered. Build a Reactor and/or Repeater "
+ "in order to power it.";
+ shortMsg = "This buildable is not powered\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NORMAL:
+ longMsg = "Cannot build on this surface. The surface is too steep or "
+ "unsuitable to build on. Please choose another site for this "
+ "structure.";
+ shortMsg = "Cannot build on this surface\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_REACTOR:
+ longMsg = "There can only be one Reactor. Destroy the existing one if you "
+ "wish to move it.";
+ shortMsg = "There can only be one Reactor\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_REPEATER:
+ longMsg = "There is no power here. If available, a Repeater may be used to "
+ "transmit power to this location.";
+ shortMsg = "There is no power here\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NODCC:
+ longMsg = "There is no Defense Computer. A Defense Computer is needed to "
+ "build this.";
+ shortMsg = "There is no Defense Computer\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_TNODEWARN:
+ longMsg = "WARNING: This Telenode will not be powered. Build near a power "
+ "structure to prevent seeing this message again.";
+ shortMsg = "This Telenode will not be powered\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_RPTWARN:
+ longMsg = "WARNING: This Repeater will not be powered as there is no parent "
+ "Reactor providing power. Build a Reactor.";
+ shortMsg = "This Repeater will not be powered\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_RPTWARN2:
+ longMsg = "This area already has power. A Repeater is not required here.";
+ shortMsg = "This area already has power\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOSLOTS:
+ longMsg = "You have no room to carry this. Please sell any conflicting "
+ "upgrades before purchasing this item.";
+ shortMsg = "You have no room to carry this\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOFUNDS:
+ longMsg = "Insufficient funds. You do not have enough credits to perform "
+ "this action.";
+ shortMsg = "Insufficient funds\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_ITEMHELD:
+ longMsg = "You already hold this item. It is not possible to carry multiple "
+ "items of the same type.";
+ shortMsg = "You already hold this item\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOARMOURYHERE:
+ longMsg = "You must be near a powered Armoury in order to purchase "
+ "weapons, upgrades or non-energy ammunition.";
+ shortMsg = "You must be near a powered Armoury\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOENERGYAMMOHERE:
+ longMsg = "You must be near an Armoury, Reactor or Repeater in order "
+ "to purchase energy ammunition.";
+ shortMsg = "You must be near an Armoury, Reactor or Repeater\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOROOMBSUITON:
+ longMsg = "There is not enough room here to put on a Battle Suit. "
+ "Make sure you have enough head room to climb in.";
+ shortMsg = "Not enough room here to put on a Battle Suit\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOROOMBSUITOFF:
+ longMsg = "There is not enough room here to take off your Battle Suit. "
+ "Make sure you have enough head room to climb out.";
+ shortMsg = "Not enough room here to take off your Battle Suit\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_ARMOURYBUILDTIMER:
+ longMsg = "You are not allowed to buy or sell weapons until your "
+ "build timer has expired.";
+ shortMsg = "You can not buy or sell weapos until your build timer "
+ "expires\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+
+ //===============================
+
+ case MN_A_NOROOM:
+ longMsg = "There is no room to build here. Move until the structure turns "
+ "translucent green indicating a valid build location.";
+ shortMsg = "There is no room to build here\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOCREEP:
+ longMsg = "There is no creep here. You must build near existing Eggs or "
+ "the Overmind. Alien structures will not support themselves.";
+ shortMsg = "There is no creep here\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOOVMND:
+ longMsg = "There is no Overmind. An Overmind must be built to control "
+ "the structure you tried to place";
+ shortMsg = "There is no Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_OVERMIND:
+ longMsg = "There can only be one Overmind. Destroy the existing one if you "
+ "wish to move it.";
+ shortMsg = "There can only be one Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL:
+ longMsg = "There can only be one Hovel. Destroy the existing one if you "
+ "wish to move it.";
+ shortMsg = "There can only be one Hovel\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOASSERT:
+ longMsg = "The Overmind cannot control any more structures. Destroy existing "
+ "structures to build more.";
+ shortMsg = "The Overmind cannot control any more structures\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_SPWNWARN:
+ longMsg = "WARNING: This spawn will not be controlled by an Overmind. "
+ "Build an Overmind to prevent seeing this message again.";
+ shortMsg = "This spawn will not be controlled by an Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NORMAL:
+ longMsg = "Cannot build on this surface. This surface is too steep or "
+ "unsuitable to build on. Please choose another site for this "
+ "structure.";
+ shortMsg = "Cannot build on this surface\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOEROOM:
+ longMsg = "There is no room to evolve here. Move away from walls or other "
+ "nearby objects and try again.";
+ cmd = "menu tremulous_alien_dialog\n";
+ shortMsg = "There is no room to evolve here\n";
+ break;
+
+ case MN_A_TOOCLOSE:
+ longMsg = "This location is too close to the enemy to evolve. Move away "
+ "until you are no longer aware of the enemy's presence and try "
+ "again.";
+ shortMsg = "This location is too close to the enemy to evolve\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOOVMND_EVOLVE:
+ longMsg = "There is no Overmind. An Overmind must be built to allow "
+ "you to upgrade.";
+ shortMsg = "There is no Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_EVOLVEBUILDTIMER:
+ longMsg = "You cannot Evolve until your build timer has expired.";
+ shortMsg = "You cannot Evolve until your build timer expires\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL_OCCUPIED:
+ longMsg = "This Hovel is already occupied by another builder.";
+ shortMsg = "This Hovel is already occupied by another builder\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL_BLOCKED:
+ longMsg = "The exit to this Hovel is currently blocked. Please wait until it "
+ "becomes clear then try again.";
+ shortMsg = "The exit to this Hovel is currently blocked\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL_EXIT:
+ longMsg = "The exit to this Hovel would always be blocked. Please choose "
+ "a more suitable location.";
+ shortMsg = "The exit to this Hovel would always be blocked\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_INFEST:
+ trap_Cvar_Set( "ui_currentClass", va( "%d %d", cg.snap->ps.stats[ STAT_PCLASS ],
+ cg.snap->ps.persistant[ PERS_CREDIT ] ) );
+ cmd = "menu tremulous_alienupgrade\n";
+ break;
+
+ default:
+ Com_Printf( "cgame: debug: no such menu %d\n", menu );
+ }
+
+ if( !cg_disableWarningDialogs.integer || !shortMsg )
+ {
+ // Player either wants dialog window or there's no short message
+ if( cmd )
+ {
+ if( longMsg )
+ trap_Cvar_Set( "ui_dialog", longMsg );
+
+ trap_SendConsoleCommand( cmd );
+ }
+ }
+ else
+ {
+ // There is short message and player wants it
+ CG_Printf( shortMsg );
+ }
+}
+
+/*
+=================
+CG_ServerCommand
+
+The string has been tokenized and can be retrieved with
+Cmd_Argc() / Cmd_Argv()
+=================
+*/
+static void CG_ServerCommand( void )
+{
+ const char *cmd;
+ char text[ MAX_SAY_TEXT ];
+
+ cmd = CG_Argv( 0 );
+
+ if( !cmd[ 0 ] )
+ {
+ // server claimed the command
+ return;
+ }
+
+ if( !strcmp( cmd, "cp" ) )
+ {
+ CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
+ return;
+ }
+
+ if( !strcmp( cmd, "cs" ) )
+ {
+ CG_ConfigStringModified( );
+ return;
+ }
+
+ if( !strcmp( cmd, "print" ) )
+ {
+ CG_Printf( "%s", CG_Argv( 1 ) );
+ return;
+ }
+
+ if( !strcmp( cmd, "chat" ) )
+ {
+ if( !cg_teamChatsOnly.integer )
+ {
+ Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
+ if( Q_stricmpn( text, "[skipnotify]", 12 ) )
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ CG_RemoveChatEscapeChar( text );
+ CG_Printf( "%s\n", text );
+ }
+
+ return;
+ }
+
+ if( !strcmp( cmd, "tchat" ) )
+ {
+ Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
+ if( Q_stricmpn( text, "[skipnotify]", 12 ) )
+ {
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND );
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND );
+ else
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ }
+ CG_RemoveChatEscapeChar( text );
+ CG_Printf( "%s\n", text );
+ return;
+ }
+
+ if( !strcmp( cmd, "scores" ) )
+ {
+ CG_ParseScores( );
+ return;
+ }
+
+ if( !strcmp( cmd, "tinfo" ) )
+ {
+ CG_ParseTeamInfo( );
+ return;
+ }
+
+ if( !strcmp( cmd, "map_restart" ) )
+ {
+ CG_MapRestart( );
+ return;
+ }
+
+ if( Q_stricmp( cmd, "remapShader" ) == 0 )
+ {
+ if( trap_Argc( ) == 4 )
+ trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) );
+ }
+
+ // clientLevelShot is sent before taking a special screenshot for
+ // the menu system during development
+ if( !strcmp( cmd, "clientLevelShot" ) )
+ {
+ cg.levelShot = qtrue;
+ return;
+ }
+
+ //the server has triggered a menu
+ if( !strcmp( cmd, "servermenu" ) )
+ {
+ if( trap_Argc( ) == 2 && !cg.demoPlayback )
+ CG_Menu( atoi( CG_Argv( 1 ) ) );
+
+ return;
+ }
+
+ //the server thinks this client should close all menus
+ if( !strcmp( cmd, "serverclosemenus" ) )
+ {
+ trap_SendConsoleCommand( "closemenus\n" );
+ return;
+ }
+
+ //poison cloud effect needs to be reliable
+ if( !strcmp( cmd, "poisoncloud" ) )
+ {
+ cg.poisonedTime = cg.time;
+
+ if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) )
+ {
+ cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS );
+ CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity );
+ CG_AttachToCent( &cg.poisonCloudPS->attachment );
+ }
+
+ return;
+ }
+
+ if( !strcmp( cmd, "weaponswitch" ) )
+ {
+ CG_Printf( "client weaponswitch\n" );
+ if( trap_Argc( ) == 2 )
+ {
+ cg.weaponSelect = atoi( CG_Argv( 1 ) );
+ cg.weaponSelectTime = cg.time;
+ }
+
+ return;
+ }
+
+ // server requests a ptrc
+ if( !strcmp( cmd, "ptrcrequest" ) )
+ {
+ int code = CG_ReadPTRCode( );
+
+ trap_SendClientCommand( va( "ptrcverify %d", code ) );
+ return;
+ }
+
+ // server issues a ptrc
+ if( !strcmp( cmd, "ptrcissue" ) )
+ {
+ if( trap_Argc( ) == 2 )
+ {
+ int code = atoi( CG_Argv( 1 ) );
+
+ CG_WritePTRCode( code );
+ }
+
+ return;
+ }
+
+ // reply to ptrcverify
+ if( !strcmp( cmd, "ptrcconfirm" ) )
+ {
+ trap_SendConsoleCommand( "menu ptrc_popmenu\n" );
+
+ return;
+ }
+
+ CG_Printf( "Unknown client game command: %s\n", cmd );
+}
+
+
+/*
+====================
+CG_ExecuteNewServerCommands
+
+Execute all of the server commands that were received along
+with this this snapshot.
+====================
+*/
+void CG_ExecuteNewServerCommands( int latestSequence )
+{
+ while( cgs.serverCommandSequence < latestSequence )
+ {
+ if( trap_GetServerCommand( ++cgs.serverCommandSequence ) )
+ CG_ServerCommand( );
+ }
+}
diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c
new file mode 100644
index 0000000..84b419f
--- /dev/null
+++ b/src/cgame/cg_snapshot.c
@@ -0,0 +1,464 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_snapshot.c -- things that happen on snapshot transition,
+// not necessarily every single rendered frame
+
+
+#include "cg_local.h"
+
+/*
+==================
+CG_ResetEntity
+==================
+*/
+static void CG_ResetEntity( centity_t *cent )
+{
+ // if the previous snapshot this entity was updated in is at least
+ // an event window back in time then we can reset the previous event
+ if( cent->snapShotTime < cg.time - EVENT_VALID_MSEC )
+ cent->previousEvent = 0;
+
+ cent->trailTime = cg.snap->serverTime;
+
+ VectorCopy( cent->currentState.origin, cent->lerpOrigin );
+ VectorCopy( cent->currentState.angles, cent->lerpAngles );
+
+ if( cent->currentState.eType == ET_PLAYER )
+ CG_ResetPlayerEntity( cent );
+}
+
+/*
+===============
+CG_TransitionEntity
+
+cent->nextState is moved to cent->currentState and events are fired
+===============
+*/
+static void CG_TransitionEntity( centity_t *cent )
+{
+ cent->currentState = cent->nextState;
+ cent->currentValid = qtrue;
+
+ // reset if the entity wasn't in the last frame or was teleported
+ if( !cent->interpolate )
+ CG_ResetEntity( cent );
+
+ // clear the next state. if will be set by the next CG_SetNextSnap
+ cent->interpolate = qfalse;
+
+ // check for events
+ CG_CheckEvents( cent );
+}
+
+
+/*
+==================
+CG_SetInitialSnapshot
+
+This will only happen on the very first snapshot, or
+on tourney restarts. All other times will use
+CG_TransitionSnapshot instead.
+
+FIXME: Also called by map_restart?
+==================
+*/
+void CG_SetInitialSnapshot( snapshot_t *snap )
+{
+ int i;
+ centity_t *cent;
+ entityState_t *state;
+
+ cg.snap = snap;
+
+ BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse );
+
+ // sort out solid entities
+ CG_BuildSolidList( );
+
+ CG_ExecuteNewServerCommands( snap->serverCommandSequence );
+
+ // set our local weapon selection pointer to
+ // what the server has indicated the current weapon is
+ CG_Respawn( );
+
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ state = &cg.snap->entities[ i ];
+ cent = &cg_entities[ state->number ];
+
+ memcpy( &cent->currentState, state, sizeof( entityState_t ) );
+ //cent->currentState = *state;
+ cent->interpolate = qfalse;
+ cent->currentValid = qtrue;
+
+ CG_ResetEntity( cent );
+
+ // check for events
+ CG_CheckEvents( cent );
+ }
+}
+
+
+/*
+===================
+CG_TransitionSnapshot
+
+The transition point from snap to nextSnap has passed
+===================
+*/
+static void CG_TransitionSnapshot( void )
+{
+ centity_t *cent;
+ snapshot_t *oldFrame;
+ int i;
+
+ if( !cg.snap )
+ CG_Error( "CG_TransitionSnapshot: NULL cg.snap" );
+
+ if( !cg.nextSnap )
+ CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" );
+
+ // execute any server string commands before transitioning entities
+ CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence );
+
+ // if we had a map_restart, set everthing with initial
+ if( !cg.snap ) { } //TA: ?
+
+ // clear the currentValid flag for all entities in the existing snapshot
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+ cent->currentValid = qfalse;
+ }
+
+ // move nextSnap to snap and do the transitions
+ oldFrame = cg.snap;
+ cg.snap = cg.nextSnap;
+
+ BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse );
+ cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse;
+
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+ CG_TransitionEntity( cent );
+
+ // remember time of snapshot this entity was last updated in
+ cent->snapShotTime = cg.snap->serverTime;
+ }
+
+ cg.nextSnap = NULL;
+
+ // check for playerstate transition events
+ if( oldFrame )
+ {
+ playerState_t *ops, *ps;
+
+ ops = &oldFrame->ps;
+ ps = &cg.snap->ps;
+ // teleporting checks are irrespective of prediction
+ if( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT )
+ cg.thisFrameTeleport = qtrue; // will be cleared by prediction code
+
+ // if we are not doing client side movement prediction for any
+ // reason, then the client events and view changes will be issued now
+ if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) ||
+ cg_nopredict.integer || cg_synchronousClients.integer )
+ CG_TransitionPlayerState( ps, ops );
+ }
+}
+
+
+/*
+===================
+CG_SetNextSnap
+
+A new snapshot has just been read in from the client system.
+===================
+*/
+static void CG_SetNextSnap( snapshot_t *snap )
+{
+ int num;
+ entityState_t *es;
+ centity_t *cent;
+
+ cg.nextSnap = snap;
+
+ BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse );
+ cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue;
+
+ // check for extrapolation errors
+ for( num = 0 ; num < snap->numEntities ; num++ )
+ {
+ es = &snap->entities[ num ];
+ cent = &cg_entities[ es->number ];
+
+ memcpy( &cent->nextState, es, sizeof( entityState_t ) );
+ //cent->nextState = *es;
+
+ // if this frame is a teleport, or the entity wasn't in the
+ // previous frame, don't interpolate
+ if( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) )
+ cent->interpolate = qfalse;
+ else
+ cent->interpolate = qtrue;
+ }
+
+ // if the next frame is a teleport for the playerstate, we
+ // can't interpolate during demos
+ if( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) )
+ cg.nextFrameTeleport = qtrue;
+ else
+ cg.nextFrameTeleport = qfalse;
+
+ // if changing follow mode, don't interpolate
+ if( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum )
+ cg.nextFrameTeleport = qtrue;
+
+ // if changing server restarts, don't interpolate
+ if( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT )
+ cg.nextFrameTeleport = qtrue;
+
+ // sort out solid entities
+ CG_BuildSolidList( );
+}
+
+
+#ifdef MODULE_INTERFACE_11
+static moduleAlternateSnapshot_t moduleAlternateSnapshot;
+
+static qboolean CG_GetModuleAlternateSnapshot( int snapshotNumber, snapshot_t *snap )
+{
+ moduleAlternateSnapshot_t *alt = &moduleAlternateSnapshot;
+ int r = trap_GetSnapshot( snapshotNumber, alt );
+
+ if( r )
+ {
+ int i;
+
+ snap->snapFlags = alt->snapFlags;
+ snap->ping = alt->ping;
+ snap->serverTime = alt->serverTime;
+ snap->numEntities = alt->numEntities;
+ snap->numServerCommands = alt->numServerCommands;
+ snap->serverCommandSequence = alt->serverCommandSequence;
+ memcpy( &snap->areamask, &alt->areamask, sizeof( snap->areamask ) );
+
+#define PSFO(x) ((size_t)&(((playerState_t*)0)->x))
+ memcpy( &snap->ps.commandTime, &alt->ps.commandTime, PSFO(tauntTimer) - PSFO(commandTime) );
+ memcpy( &snap->ps.movementDir, &alt->ps.movementDir, PSFO(ammo) - PSFO(movementDir) );
+ memcpy( &snap->ps.generic1, &alt->ps.generic1, PSFO(entityEventSequence) - PSFO(generic1) );
+
+ snap->ps.weaponAnim = alt->ps.ammo[0] & 0xFF;
+ snap->ps.pm_flags |= ( alt->ps.ammo[0] & 0xFF00 ) << 8;
+ snap->ps.ammo = alt->ps.ammo[1] & 0xFFF;
+ snap->ps.clips = ( alt->ps.ammo[1] & 0xF000 ) >> 12;
+ snap->ps.tauntTimer = alt->ps.ammo[2] & 0xFFF;
+ snap->ps.generic1 |= ( alt->ps.ammo[2] & 0x3000 ) >> 4;
+
+ for( i = 0; i < alt->numEntities; ++i )
+ {
+ entityState_t *s = &snap->entities[ i ];
+ const moduleAlternateEntityState_t *a = &alt->entities[ i ];
+
+#define ESFO(x) ((size_t)&(((entityState_t*)0)->x))
+ memcpy( &s->number, &a->number, ESFO(weaponAnim) - ESFO(number) );
+
+ s->weaponAnim = 0;
+ s->generic1 = a->generic1;
+ }
+ }
+
+ return r;
+}
+#endif
+
+/*
+========================
+CG_ReadNextSnapshot
+
+This is the only place new snapshots are requested
+This may increment cgs.processedSnapshotNum multiple
+times if the client system fails to return a
+valid snapshot.
+========================
+*/
+static snapshot_t *CG_ReadNextSnapshot( void )
+{
+ qboolean r;
+ snapshot_t *dest;
+
+ if( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 )
+ {
+ CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i",
+ cg.latestSnapshotNum, cgs.processedSnapshotNum );
+ }
+
+ while( cgs.processedSnapshotNum < cg.latestSnapshotNum )
+ {
+ // decide which of the two slots to load it into
+ if( cg.snap == &cg.activeSnapshots[ 0 ] )
+ dest = &cg.activeSnapshots[ 1 ];
+ else
+ dest = &cg.activeSnapshots[ 0 ];
+
+ // try to read the snapshot from the client system
+ cgs.processedSnapshotNum++;
+#ifdef MODULE_INTERFACE_11
+ r = CG_GetModuleAlternateSnapshot( cgs.processedSnapshotNum, dest );
+#else
+ r = trap_GetSnapshot( cgs.processedSnapshotNum, dest );
+#endif
+
+ // FIXME: why would trap_GetSnapshot return a snapshot with the same server time
+ if( cg.snap && r && dest->serverTime == cg.snap->serverTime )
+ {
+ //continue;
+ }
+
+ // if it succeeded, return
+ if( r )
+ {
+ CG_AddLagometerSnapshotInfo( dest );
+ return dest;
+ }
+
+ // a GetSnapshot will return failure if the snapshot
+ // never arrived, or is so old that its entities
+ // have been shoved off the end of the circular
+ // buffer in the client system.
+
+ // record as a dropped packet
+ CG_AddLagometerSnapshotInfo( NULL );
+
+ // If there are additional snapshots, continue trying to
+ // read them.
+ }
+
+ // nothing left to read
+ return NULL;
+}
+
+
+/*
+============
+CG_ProcessSnapshots
+
+We are trying to set up a renderable view, so determine
+what the simulated time is, and try to get snapshots
+both before and after that time if available.
+
+If we don't have a valid cg.snap after exiting this function,
+then a 3D game view cannot be rendered. This should only happen
+right after the initial connection. After cg.snap has been valid
+once, it will never turn invalid.
+
+Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot
+hasn't arrived yet (it becomes an extrapolating situation instead
+of an interpolating one)
+
+============
+*/
+void CG_ProcessSnapshots( void )
+{
+ snapshot_t *snap;
+ int n;
+
+ // see what the latest snapshot the client system has is
+ trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime );
+
+ if( n != cg.latestSnapshotNum )
+ {
+ if( n < cg.latestSnapshotNum )
+ {
+ // this should never happen
+ CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" );
+ }
+
+ cg.latestSnapshotNum = n;
+ }
+
+ // If we have yet to receive a snapshot, check for it.
+ // Once we have gotten the first snapshot, cg.snap will
+ // always have valid data for the rest of the game
+ while( !cg.snap )
+ {
+ snap = CG_ReadNextSnapshot( );
+
+ if( !snap )
+ {
+ // we can't continue until we get a snapshot
+ return;
+ }
+
+ // set our weapon selection to what
+ // the playerstate is currently using
+ if( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) )
+ CG_SetInitialSnapshot( snap );
+ }
+
+ // loop until we either have a valid nextSnap with a serverTime
+ // greater than cg.time to interpolate towards, or we run
+ // out of available snapshots
+ do
+ {
+ // if we don't have a nextframe, try and read a new one in
+ if( !cg.nextSnap )
+ {
+ snap = CG_ReadNextSnapshot( );
+
+ // if we still don't have a nextframe, we will just have to
+ // extrapolate
+ if( !snap )
+ break;
+
+ CG_SetNextSnap( snap );
+
+ // if time went backwards, we have a level restart
+ if( cg.nextSnap->serverTime < cg.snap->serverTime )
+ CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
+ }
+
+ // if our time is < nextFrame's, we have a nice interpolating state
+ if( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime )
+ break;
+
+ // we have passed the transition from nextFrame to frame
+ CG_TransitionSnapshot( );
+ } while( 1 );
+
+ // assert our valid conditions upon exiting
+ if( cg.snap == NULL )
+ CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" );
+
+ if( cg.time < cg.snap->serverTime )
+ {
+ // this can happen right after a vid_restart
+ cg.time = cg.snap->serverTime;
+ }
+
+ if( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time )
+ CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" );
+}
+
diff --git a/src/cgame/cg_syscalls.asm b/src/cgame/cg_syscalls.asm
new file mode 100644
index 0000000..2537c91
--- /dev/null
+++ b/src/cgame/cg_syscalls.asm
@@ -0,0 +1,121 @@
+code
+
+equ trap_Print -1
+equ trap_Error -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Register -4
+equ trap_Cvar_Update -5
+equ trap_Cvar_Set -6
+equ trap_Cvar_VariableStringBuffer -7
+equ trap_Argc -8
+equ trap_Argv -9
+equ trap_Args -10
+equ trap_FS_FOpenFile -11
+equ trap_FS_Read -12
+equ trap_FS_Write -13
+equ trap_FS_FCloseFile -14
+equ trap_SendConsoleCommand -15
+equ trap_AddCommand -16
+equ trap_SendClientCommand -17
+equ trap_UpdateScreen -18
+equ trap_CM_LoadMap -19
+equ trap_CM_NumInlineModels -20
+equ trap_CM_InlineModel -21
+equ trap_CM_LoadModel -22
+equ trap_CM_TempBoxModel -23
+equ trap_CM_PointContents -24
+equ trap_CM_TransformedPointContents -25
+equ trap_CM_BoxTrace -26
+equ trap_CM_TransformedBoxTrace -27
+equ trap_CM_MarkFragments -28
+equ trap_S_StartSound -29
+equ trap_S_StartLocalSound -30
+equ trap_S_ClearLoopingSounds -31
+equ trap_S_AddLoopingSound -32
+equ trap_S_UpdateEntityPosition -33
+equ trap_S_Respatialize -34
+equ trap_S_RegisterSound -35
+equ trap_S_StartBackgroundTrack -36
+equ trap_R_LoadWorldMap -37
+equ trap_R_RegisterModel -38
+equ trap_R_RegisterSkin -39
+equ trap_R_RegisterShader -40
+equ trap_R_ClearScene -41
+equ trap_R_AddRefEntityToScene -42
+equ trap_R_AddPolyToScene -43
+equ trap_R_AddLightToScene -44
+equ trap_R_RenderScene -45
+equ trap_R_SetColor -46
+equ trap_R_SetClipRegion -47
+equ trap_R_DrawStretchPic -48
+equ trap_R_ModelBounds -49
+equ trap_R_LerpTag -50
+equ trap_GetGlconfig -51
+equ trap_GetGameState -52
+equ trap_GetCurrentSnapshotNumber -53
+equ trap_GetSnapshot -54
+equ trap_GetServerCommand -55
+equ trap_GetCurrentCmdNumber -56
+equ trap_GetUserCmd -57
+equ trap_SetUserCmdValue -58
+equ trap_R_RegisterShaderNoMip -59
+equ trap_MemoryRemaining -60
+equ trap_R_RegisterFont -61
+equ trap_Key_IsDown -62
+equ trap_Key_GetCatcher -63
+equ trap_Key_SetCatcher -64
+equ trap_Key_GetKey -65
+equ trap_S_StopBackgroundTrack -66
+equ trap_RealTime -67
+equ trap_SnapVector -68
+equ trap_RemoveCommand -69
+equ trap_R_LightForPoint -70
+equ trap_CIN_PlayCinematic -71
+equ trap_CIN_StopCinematic -72
+equ trap_CIN_RunCinematic -73
+equ trap_CIN_DrawCinematic -74
+equ trap_CIN_SetExtents -75
+equ trap_R_RemapShader -76
+equ trap_S_AddRealLoopingSound -77
+equ trap_S_StopLoopingSound -78
+equ trap_CM_TempCapsuleModel -79
+equ trap_CM_CapsuleTrace -80
+equ trap_CM_TransformedCapsuleTrace -81
+equ trap_R_AddAdditiveLightToScene -82
+equ trap_GetEntityToken -83
+equ trap_R_AddPolysToScene -84
+equ trap_R_inPVS -85
+equ trap_FS_Seek -86
+equ trap_FS_GetFileList -87
+equ trap_LiteralArgs -88
+equ trap_CM_BiSphereTrace -89
+equ trap_CM_TransformedBiSphereTrace -90
+equ trap_GetDemoState -91
+equ trap_GetDemoPos -92
+equ trap_GetDemoName -93
+equ trap_Key_KeynumToStringBuf -94
+equ trap_Key_GetBindingBuf -95
+equ trap_Key_SetBinding -96
+
+equ trap_Parse_AddGlobalDefine -97
+equ trap_Parse_LoadSource -98
+equ trap_Parse_FreeSource -99
+equ trap_Parse_ReadToken -100
+equ trap_Parse_SourceFileAndLine -101
+equ trap_Key_SetOverstrikeMode -102
+equ trap_Key_GetOverstrikeMode -103
+
+equ trap_S_SoundDuration -104
+
+equ memset -201
+equ memcpy -202
+equ strncpy -203
+equ sin -204
+equ cos -205
+equ atan2 -206
+equ sqrt -207
+equ floor -208
+equ ceil -209
+equ testPrintInt -210
+equ testPrintFloat -211
+
diff --git a/src/cgame/cg_syscalls.c b/src/cgame/cg_syscalls.c
new file mode 100644
index 0000000..aa42019
--- /dev/null
+++ b/src/cgame/cg_syscalls.c
@@ -0,0 +1,574 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_syscalls.c -- this file is only included when building a dll
+// cg_syscalls.asm is included instead when building a qvm
+
+
+#include "cg_local.h"
+
+static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1;
+
+
+Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) )
+{
+ syscall = syscallptr;
+}
+
+
+int PASSFLOAT( float x )
+{
+ float floatTemp;
+ floatTemp = x;
+ return *(int *)&floatTemp;
+}
+
+void trap_Print( const char *fmt )
+{
+ syscall( CG_PRINT, fmt );
+}
+
+void trap_Error( const char *fmt )
+{
+ syscall( CG_ERROR, fmt );
+}
+
+int trap_Milliseconds( void )
+{
+ return syscall( CG_MILLISECONDS );
+}
+
+void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags )
+{
+ syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags );
+}
+
+void trap_Cvar_Update( vmCvar_t *vmCvar )
+{
+ syscall( CG_CVAR_UPDATE, vmCvar );
+}
+
+void trap_Cvar_Set( const char *var_name, const char *value )
+{
+ syscall( CG_CVAR_SET, var_name, value );
+}
+
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize )
+{
+ syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize );
+}
+
+int trap_Argc( void )
+{
+ return syscall( CG_ARGC );
+}
+
+void trap_Argv( int n, char *buffer, int bufferLength )
+{
+ syscall( CG_ARGV, n, buffer, bufferLength );
+}
+
+void trap_Args( char *buffer, int bufferLength )
+{
+ syscall( CG_ARGS, buffer, bufferLength );
+}
+
+void trap_LiteralArgs( char *buffer, int bufferLength )
+{
+ syscall( CG_LITERAL_ARGS, buffer, bufferLength );
+}
+
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode )
+{
+ return syscall( CG_FS_FOPENFILE, qpath, f, mode );
+}
+
+void trap_FS_Read( void *buffer, int len, fileHandle_t f )
+{
+ syscall( CG_FS_READ, buffer, len, f );
+}
+
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f )
+{
+ syscall( CG_FS_WRITE, buffer, len, f );
+}
+
+void trap_FS_FCloseFile( fileHandle_t f )
+{
+ syscall( CG_FS_FCLOSEFILE, f );
+}
+
+void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin )
+{
+ syscall( CG_FS_SEEK, f, offset, origin );
+}
+
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize )
+{
+ return syscall( CG_FS_GETFILELIST, path, extension, listbuf, bufsize );
+}
+
+void trap_SendConsoleCommand( const char *text )
+{
+ syscall( CG_SENDCONSOLECOMMAND, text );
+}
+
+void trap_AddCommand( const char *cmdName )
+{
+ syscall( CG_ADDCOMMAND, cmdName );
+}
+
+void trap_RemoveCommand( const char *cmdName )
+{
+ syscall( CG_REMOVECOMMAND, cmdName );
+}
+
+void trap_SendClientCommand( const char *s )
+{
+ syscall( CG_SENDCLIENTCOMMAND, s );
+}
+
+void trap_UpdateScreen( void )
+{
+ syscall( CG_UPDATESCREEN );
+}
+
+void trap_CM_LoadMap( const char *mapname )
+{
+ syscall( CG_CM_LOADMAP, mapname );
+}
+
+int trap_CM_NumInlineModels( void )
+{
+ return syscall( CG_CM_NUMINLINEMODELS );
+}
+
+clipHandle_t trap_CM_InlineModel( int index )
+{
+ return syscall( CG_CM_INLINEMODEL, index );
+}
+
+clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs )
+{
+ return syscall( CG_CM_TEMPBOXMODEL, mins, maxs );
+}
+
+clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs )
+{
+ return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs );
+}
+
+int trap_CM_PointContents( const vec3_t p, clipHandle_t model )
+{
+ return syscall( CG_CM_POINTCONTENTS, p, model );
+}
+
+int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin,
+ const vec3_t angles )
+{
+ return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles );
+}
+
+void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask )
+{
+ syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask );
+}
+
+void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask )
+{
+ syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask );
+}
+
+void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles )
+{
+ syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles );
+}
+
+void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles )
+{
+ syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles );
+}
+
+void trap_CM_BiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask )
+{
+ syscall( CG_CM_BISPHERETRACE, results, start, end,
+ PASSFLOAT( startRad ), PASSFLOAT( endRad ), model, mask );
+}
+
+void trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask,
+ const vec3_t origin )
+{
+ syscall( CG_CM_TRANSFORMEDBISPHERETRACE, results, start, end, PASSFLOAT( startRad ),
+ PASSFLOAT( endRad ), model, mask, origin );
+}
+
+int trap_CM_MarkFragments( int numPoints, const vec3_t *points,
+ const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer,
+ int maxFragments, markFragment_t *fragmentBuffer )
+{
+ return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints,
+ pointBuffer, maxFragments, fragmentBuffer );
+}
+
+void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx )
+{
+ syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx );
+}
+
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum )
+{
+ syscall( CG_S_STARTLOCALSOUND, sfx, channelNum );
+}
+
+void trap_S_ClearLoopingSounds( qboolean killall )
+{
+ syscall( CG_S_CLEARLOOPINGSOUNDS, killall );
+}
+
+void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx )
+{
+ syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx );
+}
+
+void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx )
+{
+ syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx );
+}
+
+void trap_S_StopLoopingSound( int entityNum )
+{
+ syscall( CG_S_STOPLOOPINGSOUND, entityNum );
+}
+
+void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin )
+{
+ syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin );
+}
+
+void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater )
+{
+ syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater );
+}
+
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed )
+{
+ return syscall( CG_S_REGISTERSOUND, sample, compressed );
+}
+
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop )
+{
+ syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop );
+}
+
+void trap_R_LoadWorldMap( const char *mapname )
+{
+ syscall( CG_R_LOADWORLDMAP, mapname );
+}
+
+qhandle_t trap_R_RegisterModel( const char *name )
+{
+ return syscall( CG_R_REGISTERMODEL, name );
+}
+
+qhandle_t trap_R_RegisterSkin( const char *name )
+{
+ return syscall( CG_R_REGISTERSKIN, name );
+}
+
+qhandle_t trap_R_RegisterShader( const char *name )
+{
+ return syscall( CG_R_REGISTERSHADER, name );
+}
+
+qhandle_t trap_R_RegisterShaderNoMip( const char *name )
+{
+ return syscall( CG_R_REGISTERSHADERNOMIP, name );
+}
+
+void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font )
+{
+ syscall(CG_R_REGISTERFONT, fontName, pointSize, font );
+}
+
+void trap_R_ClearScene( void )
+{
+ syscall( CG_R_CLEARSCENE );
+}
+
+void trap_R_AddRefEntityToScene( const refEntity_t *re )
+{
+ syscall( CG_R_ADDREFENTITYTOSCENE, re );
+}
+
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts )
+{
+ syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts );
+}
+
+void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num )
+{
+ syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num );
+}
+
+int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir )
+{
+ return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir );
+}
+
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b )
+{
+ syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) );
+}
+
+void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b )
+{
+ syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) );
+}
+
+void trap_R_RenderScene( const refdef_t *fd )
+{
+ syscall( CG_R_RENDERSCENE, fd );
+}
+
+void trap_R_SetColor( const float *rgba )
+{
+ syscall( CG_R_SETCOLOR, rgba );
+}
+
+void trap_R_DrawStretchPic( float x, float y, float w, float h,
+ float s1, float t1, float s2, float t2, qhandle_t hShader )
+{
+ syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h),
+ PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader );
+}
+
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) {
+ syscall( CG_R_MODELBOUNDS, model, mins, maxs );
+}
+
+int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame,
+ float frac, const char *tagName )
+{
+ return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName );
+}
+
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset )
+{
+ syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset );
+}
+
+void trap_GetGlconfig( glconfig_t *glconfig )
+{
+ syscall( CG_GETGLCONFIG, glconfig );
+}
+
+void trap_GetGameState( gameState_t *gamestate )
+{
+ syscall( CG_GETGAMESTATE, gamestate );
+}
+
+void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime )
+{
+ syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime );
+}
+
+#ifdef MODULE_INTERFACE_11
+qboolean trap_GetSnapshot( int snapshotNumber, moduleAlternateSnapshot_t *snapshot )
+#else
+qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot )
+#endif
+{
+ return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot );
+}
+
+qboolean trap_GetServerCommand( int serverCommandNumber )
+{
+ return syscall( CG_GETSERVERCOMMAND, serverCommandNumber );
+}
+
+int trap_GetCurrentCmdNumber( void )
+{
+ return syscall( CG_GETCURRENTCMDNUMBER );
+}
+
+qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd )
+{
+ return syscall( CG_GETUSERCMD, cmdNumber, ucmd );
+}
+
+void trap_SetUserCmdValue( int stateValue, float sensitivityScale )
+{
+ syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT( sensitivityScale ) );
+}
+
+void testPrintInt( char *string, int i )
+{
+ syscall( CG_TESTPRINTINT, string, i );
+}
+
+void testPrintFloat( char *string, float f )
+{
+ syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) );
+}
+
+int trap_MemoryRemaining( void )
+{
+ return syscall( CG_MEMORY_REMAINING );
+}
+
+qboolean trap_Key_IsDown( int keynum )
+{
+ return syscall( CG_KEY_ISDOWN, keynum );
+}
+
+int trap_Key_GetCatcher( void )
+{
+ return syscall( CG_KEY_GETCATCHER );
+}
+
+void trap_Key_SetCatcher( int catcher )
+{
+ syscall( CG_KEY_SETCATCHER, catcher );
+}
+
+int trap_Key_GetKey( const char *binding )
+{
+ return syscall( CG_KEY_GETKEY, binding );
+}
+
+int trap_Parse_AddGlobalDefine( char *define )
+{
+ return syscall( CG_PARSE_ADD_GLOBAL_DEFINE, define );
+}
+
+int trap_Parse_LoadSource( const char *filename )
+{
+ return syscall( CG_PARSE_LOAD_SOURCE, filename );
+}
+
+int trap_Parse_FreeSource( int handle )
+{
+ return syscall( CG_PARSE_FREE_SOURCE, handle );
+}
+
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token )
+{
+ return syscall( CG_PARSE_READ_TOKEN, handle, pc_token );
+}
+
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line )
+{
+ return syscall( CG_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line );
+}
+
+void trap_S_StopBackgroundTrack( void )
+{
+ syscall( CG_S_STOPBACKGROUNDTRACK );
+}
+
+int trap_RealTime(qtime_t *qtime)
+{
+ return syscall( CG_REAL_TIME, qtime );
+}
+
+void trap_SnapVector( float *v )
+{
+ syscall( CG_SNAPVECTOR, v );
+}
+
+// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate)
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits )
+{
+ return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits);
+}
+
+// stops playing the cinematic and ends it. should always return FMV_EOF
+// cinematics must be stopped in reverse order of when they are started
+e_status trap_CIN_StopCinematic( int handle )
+{
+ return syscall(CG_CIN_STOPCINEMATIC, handle);
+}
+
+
+// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached.
+e_status trap_CIN_RunCinematic( int handle )
+{
+ return syscall(CG_CIN_RUNCINEMATIC, handle);
+}
+
+
+// draws the current frame
+void trap_CIN_DrawCinematic( int handle )
+{
+ syscall(CG_CIN_DRAWCINEMATIC, handle);
+}
+
+
+// allows you to resize the animation dynamically
+void trap_CIN_SetExtents( int handle, int x, int y, int w, int h )
+{
+ syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h);
+}
+
+int trap_GetDemoState( void )
+{
+ return syscall( CG_GETDEMOSTATE );
+}
+
+int trap_GetDemoPos( void )
+{
+ return syscall( CG_GETDEMOPOS );
+}
+
+void trap_GetDemoName( char *buffer, int size )
+{
+ syscall( CG_GETDEMONAME, buffer, size );
+}
+
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) {
+ syscall( CG_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) {
+ syscall( CG_KEY_GETBINDINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_SetBinding( int keynum, const char *binding ) {
+ syscall( CG_KEY_SETBINDING, keynum, binding );
+}
+
diff --git a/src/cgame/cg_syscalls_11.asm b/src/cgame/cg_syscalls_11.asm
new file mode 100644
index 0000000..0893ebc
--- /dev/null
+++ b/src/cgame/cg_syscalls_11.asm
@@ -0,0 +1,115 @@
+code
+
+equ trap_Print -1
+equ trap_Error -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Register -4
+equ trap_Cvar_Update -5
+equ trap_Cvar_Set -6
+equ trap_Cvar_VariableStringBuffer -7
+equ trap_Argc -8
+equ trap_Argv -9
+equ trap_Args -10
+equ trap_FS_FOpenFile -11
+equ trap_FS_Read -12
+equ trap_FS_Write -13
+equ trap_FS_FCloseFile -14
+equ trap_SendConsoleCommand -15
+equ trap_AddCommand -16
+equ trap_SendClientCommand -17
+equ trap_UpdateScreen -18
+equ trap_CM_LoadMap -19
+equ trap_CM_NumInlineModels -20
+equ trap_CM_InlineModel -21
+equ trap_CM_LoadModel -22
+equ trap_CM_TempBoxModel -23
+equ trap_CM_PointContents -24
+equ trap_CM_TransformedPointContents -25
+equ trap_CM_BoxTrace -26
+equ trap_CM_TransformedBoxTrace -27
+equ trap_CM_MarkFragments -28
+equ trap_S_StartSound -29
+equ trap_S_StartLocalSound -30
+equ trap_S_ClearLoopingSounds -31
+equ trap_S_AddLoopingSound -32
+equ trap_S_UpdateEntityPosition -33
+equ trap_S_Respatialize -34
+equ trap_S_RegisterSound -35
+equ trap_S_StartBackgroundTrack -36
+equ trap_R_LoadWorldMap -37
+equ trap_R_RegisterModel -38
+equ trap_R_RegisterSkin -39
+equ trap_R_RegisterShader -40
+equ trap_R_ClearScene -41
+equ trap_R_AddRefEntityToScene -42
+equ trap_R_AddPolyToScene -43
+equ trap_R_AddLightToScene -44
+equ trap_R_RenderScene -45
+equ trap_R_SetColor -46
+equ trap_R_DrawStretchPic -47
+equ trap_R_ModelBounds -48
+equ trap_R_LerpTag -49
+equ trap_GetGlconfig -50
+equ trap_GetGameState -51
+equ trap_GetCurrentSnapshotNumber -52
+equ trap_GetSnapshot -53
+equ trap_GetServerCommand -54
+equ trap_GetCurrentCmdNumber -55
+equ trap_GetUserCmd -56
+equ trap_SetUserCmdValue -57
+equ trap_R_RegisterShaderNoMip -58
+equ trap_MemoryRemaining -59
+equ trap_R_RegisterFont -60
+equ trap_Key_IsDown -61
+equ trap_Key_GetCatcher -62
+equ trap_Key_SetCatcher -63
+equ trap_Key_GetKey -64
+equ trap_Parse_AddGlobalDefine -65
+equ trap_Parse_LoadSource -66
+equ trap_Parse_FreeSource -67
+equ trap_Parse_ReadToken -68
+equ trap_Parse_SourceFileAndLine -69
+equ trap_S_StopBackgroundTrack -70
+equ trap_RealTime -71
+equ trap_SnapVector -72
+equ trap_RemoveCommand -73
+equ trap_R_LightForPoint -74
+equ trap_CIN_PlayCinematic -75
+equ trap_CIN_StopCinematic -76
+equ trap_CIN_RunCinematic -77
+equ trap_CIN_DrawCinematic -78
+equ trap_CIN_SetExtents -79
+equ trap_R_RemapShader -80
+equ trap_S_AddRealLoopingSound -81
+equ trap_S_StopLoopingSound -82
+equ trap_CM_TempCapsuleModel -83
+equ trap_CM_CapsuleTrace -84
+equ trap_CM_TransformedCapsuleTrace -85
+equ trap_R_AddAdditiveLightToScene -86
+equ trap_GetEntityToken -87
+equ trap_R_AddPolysToScene -88
+equ trap_R_inPVS -89
+equ trap_FS_Seek -90
+equ trap_FS_GetFileList -91
+equ trap_LiteralArgs -92
+equ trap_CM_BiSphereTrace -93
+equ trap_CM_TransformedBiSphereTrace -94
+equ trap_GetDemoState -95
+equ trap_GetDemoPos -96
+equ trap_GetDemoName -97
+equ trap_Key_KeynumToStringBuf -98
+equ trap_Key_GetBindingBuf -99
+equ trap_Key_SetBinding -100
+
+equ memset -201
+equ memcpy -202
+equ strncpy -203
+equ sin -204
+equ cos -205
+equ atan2 -206
+equ sqrt -207
+equ floor -208
+equ ceil -209
+equ testPrintInt -210
+equ testPrintFloat -211
+
diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c
new file mode 100644
index 0000000..c8943ed
--- /dev/null
+++ b/src/cgame/cg_trails.c
@@ -0,0 +1,1500 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_trails.c -- the trail system
+
+
+#include "cg_local.h"
+
+static baseTrailSystem_t baseTrailSystems[ MAX_BASETRAIL_SYSTEMS ];
+static baseTrailBeam_t baseTrailBeams[ MAX_BASETRAIL_BEAMS ];
+static int numBaseTrailSystems = 0;
+static int numBaseTrailBeams = 0;
+
+static trailSystem_t trailSystems[ MAX_TRAIL_SYSTEMS ];
+static trailBeam_t trailBeams[ MAX_TRAIL_BEAMS ];
+
+/*
+===============
+CG_CalculateBeamNodeProperties
+
+Fills in trailBeamNode_t.textureCoord
+===============
+*/
+static void CG_CalculateBeamNodeProperties( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = NULL;
+ trailSystem_t *ts;
+ baseTrailBeam_t *btb;
+ float nodeDistances[ MAX_TRAIL_BEAM_NODES ];
+ float totalDistance = 0.0f, position = 0.0f;
+ int j, numNodes = 0;
+ float TCRange, widthRange, alphaRange;
+ vec3_t colorRange;
+ float fadeAlpha = 1.0f;
+
+ if( !tb || !tb->nodes )
+ return;
+
+ ts = tb->parent;
+ btb = tb->class;
+
+ if( ts->destroyTime > 0 && btb->fadeOutTime )
+ {
+ fadeAlpha -= ( cg.time - ts->destroyTime ) / btb->fadeOutTime;
+
+ if( fadeAlpha < 0.0f )
+ fadeAlpha = 0.0f;
+ }
+
+ TCRange = tb->class->backTextureCoord -
+ tb->class->frontTextureCoord;
+ widthRange = tb->class->backWidth -
+ tb->class->frontWidth;
+ alphaRange = tb->class->backAlpha -
+ tb->class->frontAlpha;
+ VectorSubtract( tb->class->backColor,
+ tb->class->frontColor, colorRange );
+
+ for( i = tb->nodes; i && i->next; i = i->next )
+ {
+ nodeDistances[ numNodes++ ] =
+ Distance( i->position, i->next->position );
+ }
+
+ for( j = 0; j < numNodes; j++ )
+ totalDistance += nodeDistances[ j ];
+
+ for( j = 0, i = tb->nodes; i; i = i->next, j++ )
+ {
+ if( tb->class->textureType == TBTT_STRETCH )
+ {
+ i->textureCoord = tb->class->frontTextureCoord +
+ ( ( position / totalDistance ) * TCRange );
+ }
+ else if( tb->class->textureType == TBTT_REPEAT )
+ {
+ if( tb->class->clampToBack )
+ i->textureCoord = ( totalDistance - position ) /
+ tb->class->repeatLength;
+ else
+ i->textureCoord = position / tb->class->repeatLength;
+ }
+
+ i->halfWidth = ( tb->class->frontWidth +
+ ( ( position / totalDistance ) * widthRange ) ) / 2.0f;
+ i->alpha = (byte)( (float)0xFF * ( tb->class->frontAlpha +
+ ( ( position / totalDistance ) * alphaRange ) ) * fadeAlpha );
+ VectorMA( tb->class->frontColor, ( position / totalDistance ),
+ colorRange, i->color );
+
+ position += nodeDistances[ j ];
+ }
+}
+
+/*
+===============
+CG_LightVertex
+
+Lights a particular vertex
+===============
+*/
+static void CG_LightVertex( vec3_t point, byte alpha, byte *rgba )
+{
+ int i;
+ vec3_t alight, dlight, lightdir;
+
+ trap_R_LightForPoint( point, alight, dlight, lightdir );
+ for( i = 0; i <= 2; i++ )
+ rgba[ i ] = (int)alight[ i ];
+
+ rgba[ 3 ] = alpha;
+}
+
+/*
+===============
+CG_RenderBeam
+
+Renders a beam
+===============
+*/
+static void CG_RenderBeam( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = NULL;
+ trailBeamNode_t *prev = NULL;
+ trailBeamNode_t *next = NULL;
+ vec3_t up;
+ polyVert_t verts[ ( MAX_TRAIL_BEAM_NODES - 1 ) * 4 ];
+ int numVerts = 0;
+ baseTrailBeam_t *btb;
+ trailSystem_t *ts;
+ baseTrailSystem_t *bts;
+
+ if( !tb || !tb->nodes )
+ return;
+
+ btb = tb->class;
+ ts = tb->parent;
+ bts = ts->class;
+
+ if( bts->thirdPersonOnly &&
+ ( CG_AttachmentCentNum( &ts->frontAttachment ) == cg.snap->ps.clientNum ||
+ CG_AttachmentCentNum( &ts->backAttachment ) == cg.snap->ps.clientNum ) &&
+ !cg.renderingThirdPerson )
+ return;
+
+ CG_CalculateBeamNodeProperties( tb );
+
+ i = tb->nodes;
+
+ do
+ {
+ prev = i->prev;
+ next = i->next;
+
+ if( prev && next )
+ {
+ //this node has two neighbours
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up );
+ }
+ else if( !prev && next )
+ {
+ //this is the front
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up );
+ }
+ else if( prev && !next )
+ {
+ //this is the back
+ GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up );
+ }
+ else
+ break;
+
+ if( prev )
+ {
+ VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 1.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+
+ VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 0.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+ }
+
+ if( next )
+ {
+ VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 0.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+
+ VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 1.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+ }
+
+ i = i->next;
+ } while( i );
+
+ trap_R_AddPolysToScene( tb->class->shader, 4, &verts[ 0 ], numVerts / 4 );
+}
+
+/*
+===============
+CG_AllocateBeamNode
+
+Allocates a trailBeamNode_t from a trailBeam_t's nodePool
+===============
+*/
+static trailBeamNode_t *CG_AllocateBeamNode( trailBeam_t *tb )
+{
+ baseTrailBeam_t *btb = tb->class;
+ int i;
+ trailBeamNode_t *tbn;
+
+ for( i = 0; i < MAX_TRAIL_BEAM_NODES; i++ )
+ {
+ tbn = &tb->nodePool[ i ];
+ if( !tbn->used )
+ {
+ tbn->timeLeft = btb->segmentTime;
+ tbn->prev = NULL;
+ tbn->next = NULL;
+ tbn->used = qtrue;
+ return tbn;
+ }
+ }
+
+ // no space left
+ return NULL;
+}
+
+/*
+===============
+CG_DestroyBeamNode
+
+Removes a node from a beam
+Returns the new head
+===============
+*/
+static trailBeamNode_t *CG_DestroyBeamNode( trailBeamNode_t *tbn )
+{
+ trailBeamNode_t *newHead = NULL;
+
+ if( tbn->prev )
+ {
+ if( tbn->next )
+ {
+ // node is in the middle
+ tbn->prev->next = tbn->next;
+ tbn->next->prev = tbn->prev;
+ }
+ else // node is at the back
+ tbn->prev->next = NULL;
+
+ // find the new head (shouldn't have changed)
+ newHead = tbn->prev;
+
+ while( newHead->prev )
+ newHead = newHead->prev;
+ }
+ else if( tbn->next )
+ {
+ //node is at the front
+ tbn->next->prev = NULL;
+ newHead = tbn->next;
+ }
+
+ tbn->prev = NULL;
+ tbn->next = NULL;
+ tbn->used = qfalse;
+
+ return newHead;
+}
+
+/*
+===============
+CG_FindLastBeamNode
+
+Returns the last beam node in a beam
+===============
+*/
+static trailBeamNode_t *CG_FindLastBeamNode( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = tb->nodes;
+
+ while( i && i->next )
+ i = i->next;
+
+ return i;
+}
+
+/*
+===============
+CG_CountBeamNodes
+
+Returns the number of nodes in a beam
+===============
+*/
+static int CG_CountBeamNodes( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = tb->nodes;
+ int numNodes = 0;
+
+ while( i )
+ {
+ numNodes++;
+ i = i->next;
+ }
+
+ return numNodes;
+}
+
+/*
+===============
+CG_PrependBeamNode
+
+Prepend a new beam node to the front of a beam
+Returns the new node
+===============
+*/
+static trailBeamNode_t *CG_PrependBeamNode( trailBeam_t *tb )
+{
+ trailBeamNode_t *i;
+
+ if( tb->nodes )
+ {
+ // prepend another node
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ {
+ i->next = tb->nodes;
+ tb->nodes->prev = i;
+ tb->nodes = i;
+ }
+ }
+ else //add first node
+ {
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ tb->nodes = i;
+ }
+
+ return i;
+}
+
+/*
+===============
+CG_AppendBeamNode
+
+Append a new beam node to the back of a beam
+Returns the new node
+===============
+*/
+static trailBeamNode_t *CG_AppendBeamNode( trailBeam_t *tb )
+{
+ trailBeamNode_t *last, *i;
+
+ if( tb->nodes )
+ {
+ // append another node
+ last = CG_FindLastBeamNode( tb );
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ {
+ last->next = i;
+ i->prev = last;
+ i->next = NULL;
+ }
+ }
+ else //add first node
+ {
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ tb->nodes = i;
+ }
+
+ return i;
+}
+
+/*
+===============
+CG_ApplyJitters
+===============
+*/
+static void CG_ApplyJitters( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = NULL;
+ int j;
+ baseTrailBeam_t *btb;
+ trailSystem_t *ts;
+ trailBeamNode_t *start;
+ trailBeamNode_t *end;
+
+ if( !tb || !tb->nodes )
+ return;
+
+ btb = tb->class;
+ ts = tb->parent;
+
+ for( j = 0; j < btb->numJitters; j++ )
+ {
+ if( tb->nextJitterTimes[ j ] <= cg.time )
+ {
+ for( i = tb->nodes; i; i = i->next )
+ {
+ i->jitters[ j ][ 0 ] = ( crandom( ) * btb->jitters[ j ].magnitude );
+ i->jitters[ j ][ 1 ] = ( crandom( ) * btb->jitters[ j ].magnitude );
+ }
+
+ tb->nextJitterTimes[ j ] = cg.time + btb->jitters[ j ].period;
+ }
+ }
+
+ start = tb->nodes;
+ end = CG_FindLastBeamNode( tb );
+
+ if( !btb->jitterAttachments )
+ {
+ if( CG_Attached( &ts->frontAttachment ) && start->next )
+ start = start->next;
+
+ if( CG_Attached( &ts->backAttachment ) && end->prev )
+ end = end->prev;
+ }
+
+ for( i = start; i; i = i->next )
+ {
+ vec3_t forward, right, up;
+ trailBeamNode_t *prev;
+ trailBeamNode_t *next;
+ float upJitter = 0.0f, rightJitter = 0.0f;
+
+ prev = i->prev;
+ next = i->next;
+
+ if( prev && next )
+ {
+ //this node has two neighbours
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up );
+ VectorSubtract( next->position, prev->position, forward );
+ }
+ else if( !prev && next )
+ {
+ //this is the front
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up );
+ VectorSubtract( next->position, i->position, forward );
+ }
+ else if( prev && !next )
+ {
+ //this is the back
+ GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up );
+ VectorSubtract( i->position, prev->position, forward );
+ }
+
+ VectorNormalize( forward );
+ CrossProduct( forward, up, right );
+ VectorNormalize( right );
+
+ for( j = 0; j < btb->numJitters; j++ )
+ {
+ upJitter += i->jitters[ j ][ 0 ];
+ rightJitter += i->jitters[ j ][ 1 ];
+ }
+
+ VectorMA( i->position, upJitter, up, i->position );
+ VectorMA( i->position, rightJitter, right, i->position );
+
+ if( i == end )
+ break;
+ }
+}
+
+/*
+===============
+CG_UpdateBeam
+
+Updates a beam
+===============
+*/
+static void CG_UpdateBeam( trailBeam_t *tb )
+{
+ baseTrailBeam_t *btb;
+ trailSystem_t *ts;
+ trailBeamNode_t *i;
+ int deltaTime;
+ int nodesToAdd;
+ int j;
+ int numNodes;
+
+ if( !tb )
+ return;
+
+ btb = tb->class;
+ ts = tb->parent;
+
+ deltaTime = cg.time - tb->lastEvalTime;
+ tb->lastEvalTime = cg.time;
+
+ // first make sure this beam has enough nodes
+ if( ts->destroyTime <= 0 )
+ {
+ nodesToAdd = btb->numSegments - CG_CountBeamNodes( tb ) + 1;
+
+ while( nodesToAdd-- )
+ {
+ i = CG_AppendBeamNode( tb );
+
+ if( !tb->nodes->next && CG_Attached( &ts->frontAttachment ) )
+ {
+ // this is the first node to be added
+ if( !CG_AttachmentPoint( &ts->frontAttachment, i->refPosition ) )
+ CG_DestroyTrailSystem( &ts );
+ }
+ else
+ VectorCopy( i->prev->refPosition, i->refPosition );
+ }
+ }
+
+ numNodes = CG_CountBeamNodes( tb );
+
+ for( i = tb->nodes; i; i = i->next )
+ VectorCopy( i->refPosition, i->position );
+
+ if( CG_Attached( &ts->frontAttachment ) && CG_Attached( &ts->backAttachment ) )
+ {
+ // beam between two attachments
+ vec3_t dir, front, back;
+
+ if( ts->destroyTime > 0 && ( cg.time - ts->destroyTime ) >= btb->fadeOutTime )
+ {
+ tb->valid = qfalse;
+ return;
+ }
+
+ if( !CG_AttachmentPoint( &ts->frontAttachment, front ) )
+ CG_DestroyTrailSystem( &ts );
+
+ if( !CG_AttachmentPoint( &ts->backAttachment, back ) )
+ CG_DestroyTrailSystem( &ts );
+
+ VectorSubtract( back, front, dir );
+
+ for( j = 0, i = tb->nodes; i; i = i->next, j++ )
+ {
+ float scale = (float)j / (float)( numNodes - 1 );
+
+ VectorMA( front, scale, dir, i->position );
+ }
+ }
+ else if( CG_Attached( &ts->frontAttachment ) )
+ {
+ // beam from one attachment
+
+ // cull the trail tail
+ i = CG_FindLastBeamNode( tb );
+
+ if( i && i->timeLeft >= 0 )
+ {
+ i->timeLeft -= deltaTime;
+
+ if( i->timeLeft < 0 )
+ {
+ tb->nodes = CG_DestroyBeamNode( i );
+
+ if( !tb->nodes )
+ {
+ tb->valid = qfalse;
+ return;
+ }
+
+ // if the ts has been destroyed, stop creating new nodes
+ if( ts->destroyTime <= 0 )
+ CG_PrependBeamNode( tb );
+ }
+ else if( i->timeLeft >= 0 && i->prev )
+ {
+ vec3_t dir;
+ float length;
+
+ VectorSubtract( i->refPosition, i->prev->refPosition, dir );
+ length = VectorNormalize( dir ) *
+ ( (float)i->timeLeft / (float)tb->class->segmentTime );
+
+ VectorMA( i->prev->refPosition, length, dir, i->position );
+ }
+ }
+
+ if( tb->nodes )
+ {
+ if( !CG_AttachmentPoint( &ts->frontAttachment, tb->nodes->refPosition ) )
+ CG_DestroyTrailSystem( &ts );
+
+ VectorCopy( tb->nodes->refPosition, tb->nodes->position );
+ }
+ }
+
+ CG_ApplyJitters( tb );
+}
+
+/*
+===============
+CG_ParseTrailBeamColor
+===============
+*/
+static qboolean CG_ParseTrailBeamColor( byte *c, char **text_p )
+{
+ char *token;
+ int i;
+
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) );
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_ParseTrailBeam
+
+Parse a trail beam
+===============
+*/
+static qboolean CG_ParseTrailBeam( baseTrailBeam_t *btb, char **text_p )
+{
+ char *token;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "segments" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->numSegments = atoi_neg( token, qfalse );
+
+ if( btb->numSegments >= MAX_TRAIL_BEAM_NODES )
+ {
+ btb->numSegments = MAX_TRAIL_BEAM_NODES - 1;
+ CG_Printf( S_COLOR_YELLOW "WARNING: too many segments in trail beam\n" );
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "width" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->frontWidth = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ btb->backWidth = btb->frontWidth;
+ else
+ btb->backWidth = atof_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "alpha" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->frontAlpha = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ btb->backAlpha = btb->frontAlpha;
+ else
+ btb->backAlpha = atof_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "color" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseTrailBeamColor( btb->frontColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ btb->backColor[ 0 ] = btb->frontColor[ 0 ];
+ btb->backColor[ 1 ] = btb->frontColor[ 1 ];
+ btb->backColor[ 2 ] = btb->frontColor[ 2 ];
+ }
+ else if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseTrailBeamColor( btb->backColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "segmentTime" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->segmentTime = atoi_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "fadeOutTime" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->fadeOutTime = atoi_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "shader" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ Q_strncpyz( btb->shaderName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "textureType" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "stretch" ) )
+ {
+ btb->textureType = TBTT_STRETCH;
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->frontTextureCoord = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->backTextureCoord = atof_neg( token, qfalse );
+ }
+ else if( !Q_stricmp( token, "repeat" ) )
+ {
+ btb->textureType = TBTT_REPEAT;
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "front" ) )
+ btb->clampToBack = qfalse;
+ else if( !Q_stricmp( token, "back" ) )
+ btb->clampToBack = qtrue;
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown textureType clamp \"%s\"\n", token );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->repeatLength = atof_neg( token, qfalse );
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown textureType \"%s\"\n", token );
+ break;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "realLight" ) )
+ {
+ btb->realLight = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "jitter" ) )
+ {
+ if( btb->numJitters == MAX_TRAIL_BEAM_JITTERS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: too many jitters\n", token );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->jitters[ btb->numJitters ].magnitude = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->jitters[ btb->numJitters ].period = atoi_neg( token, qfalse );
+
+ btb->numJitters++;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "jitterAttachments" ) )
+ {
+ btb->jitterAttachments = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this trail beam
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail beam\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_InitialiseBaseTrailBeam
+===============
+*/
+static void CG_InitialiseBaseTrailBeam( baseTrailBeam_t *btb )
+{
+ memset( btb, 0, sizeof( baseTrailBeam_t ) );
+
+ btb->numSegments = 1;
+ btb->frontWidth = btb->backWidth = 1.0f;
+ btb->frontAlpha = btb->backAlpha = 1.0f;
+ memset( btb->frontColor, 0xFF, sizeof( btb->frontColor ) );
+ memset( btb->backColor, 0xFF, sizeof( btb->backColor ) );
+
+ btb->segmentTime = 100;
+
+ btb->textureType = TBTT_STRETCH;
+ btb->frontTextureCoord = 0.0f;
+ btb->backTextureCoord = 1.0f;
+}
+
+/*
+===============
+CG_ParseTrailSystem
+
+Parse a trail system section
+===============
+*/
+static qboolean CG_ParseTrailSystem( baseTrailSystem_t *bts, char **text_p, const char *name )
+{
+ char *token;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ CG_InitialiseBaseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ] );
+
+ if( !CG_ParseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ], text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse trail beam\n" );
+ return qfalse;
+ }
+
+ if( bts->numBeams == MAX_BEAMS_PER_SYSTEM )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: trail system has > %d beams\n", MAX_BEAMS_PER_SYSTEM );
+ return qfalse;
+ }
+ else if( numBaseTrailBeams == MAX_BASETRAIL_BEAMS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of trail beams (%d) reached\n",
+ MAX_BASETRAIL_BEAMS );
+ return qfalse;
+ }
+ else
+ {
+ //start parsing beams again
+ bts->beams[ bts->numBeams ] = &baseTrailBeams[ numBaseTrailBeams ];
+ bts->numBeams++;
+ numBaseTrailBeams++;
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "thirdPersonOnly" ) )
+ bts->thirdPersonOnly = qtrue;
+ else if( !Q_stricmp( token, "beam" ) ) //acceptable text
+ continue;
+ else if( !Q_stricmp( token, "}" ) )
+ {
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "Parsed trail system %s\n", name );
+
+ return qtrue; //reached the end of this trail system
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail system %s\n", token, bts->name );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_ParseTrailFile
+
+Load the trail systems from a trail file
+===============
+*/
+static qboolean CG_ParseTrailFile( const char *fileName )
+{
+ char *text_p;
+ int i;
+ int len;
+ char *token;
+ char text[ 32000 ];
+ char tsName[ MAX_QPATH ];
+ qboolean tsNameSet = qfalse;
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: trail file %s too long\n", fileName );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( tsNameSet )
+ {
+ //check for name space clashes
+ for( i = 0; i < numBaseTrailSystems; i++ )
+ {
+ if( !Q_stricmp( baseTrailSystems[ i ].name, tsName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a trail system is already named %s\n", tsName );
+ return qfalse;
+ }
+ }
+
+ Q_strncpyz( baseTrailSystems[ numBaseTrailSystems ].name, tsName, MAX_QPATH );
+
+ if( !CG_ParseTrailSystem( &baseTrailSystems[ numBaseTrailSystems ], &text_p, tsName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse trail system %s\n", fileName, tsName );
+ return qfalse;
+ }
+
+ //start parsing trail systems again
+ tsNameSet = qfalse;
+
+ if( numBaseTrailSystems == MAX_BASETRAIL_SYSTEMS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of trail systems (%d) reached\n",
+ MAX_BASETRAIL_SYSTEMS );
+ return qfalse;
+ }
+ else
+ numBaseTrailSystems++;
+
+ continue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unamed trail system\n" );
+ return qfalse;
+ }
+ }
+
+ if( !tsNameSet )
+ {
+ Q_strncpyz( tsName, token, sizeof( tsName ) );
+ tsNameSet = qtrue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: trail system already named\n" );
+ return qfalse;
+ }
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_LoadTrailSystems
+
+Load trail system templates
+===============
+*/
+void CG_LoadTrailSystems( void )
+{
+ int i, numFiles, fileLen;
+ char fileList[ MAX_TRAIL_FILES * MAX_QPATH ];
+ char fileName[ MAX_QPATH ];
+ char *filePtr;
+
+ //clear out the old
+ numBaseTrailSystems = 0;
+ numBaseTrailBeams = 0;
+
+ for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ )
+ {
+ baseTrailSystem_t *bts = &baseTrailSystems[ i ];
+ memset( bts, 0, sizeof( baseTrailSystem_t ) );
+ }
+
+ for( i = 0; i < MAX_BASETRAIL_BEAMS; i++ )
+ {
+ baseTrailBeam_t *btb = &baseTrailBeams[ i ];
+ memset( btb, 0, sizeof( baseTrailBeam_t ) );
+ }
+
+ //and bring in the new
+ numFiles = trap_FS_GetFileList( "scripts", ".trail",
+ fileList, MAX_TRAIL_FILES * MAX_QPATH );
+ filePtr = fileList;
+
+ for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ strcpy( fileName, "scripts/" );
+ strcat( fileName, filePtr );
+ CG_Printf( "...loading '%s'\n", fileName );
+ CG_ParseTrailFile( fileName );
+ }
+}
+
+/*
+===============
+CG_RegisterTrailSystem
+
+Load the media that a trail system needs
+===============
+*/
+qhandle_t CG_RegisterTrailSystem( char *name )
+{
+ int i, j;
+ baseTrailSystem_t *bts;
+ baseTrailBeam_t *btb;
+
+ for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ )
+ {
+ bts = &baseTrailSystems[ i ];
+
+ if( !Q_stricmp( bts->name, name ) )
+ {
+ //already registered
+ if( bts->registered )
+ return i + 1;
+
+ for( j = 0; j < bts->numBeams; j++ )
+ {
+ btb = bts->beams[ j ];
+
+ btb->shader = trap_R_RegisterShader( btb->shaderName );
+ }
+
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "Registered trail system %s\n", name );
+
+ bts->registered = qtrue;
+
+ //avoid returning 0
+ return i + 1;
+ }
+ }
+
+ CG_Printf( S_COLOR_RED "ERROR: failed to register trail system %s\n", name );
+ return 0;
+}
+
+
+/*
+===============
+CG_SpawnNewTrailBeam
+
+Allocate a new trail beam
+===============
+*/
+static trailBeam_t *CG_SpawnNewTrailBeam( baseTrailBeam_t *btb,
+ trailSystem_t *parent )
+{
+ int i;
+ trailBeam_t *tb = NULL;
+ trailSystem_t *ts = parent;
+
+ for( i = 0; i < MAX_TRAIL_BEAMS; i++ )
+ {
+ tb = &trailBeams[ i ];
+
+ if( !tb->valid )
+ {
+ memset( tb, 0, sizeof( trailBeam_t ) );
+
+ //found a free slot
+ tb->class = btb;
+ tb->parent = ts;
+
+ tb->valid = qtrue;
+
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "TB %s created\n", ts->class->name );
+
+ break;
+ }
+ }
+
+ return tb;
+}
+
+
+/*
+===============
+CG_SpawnNewTrailSystem
+
+Spawns a new trail system
+===============
+*/
+trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle )
+{
+ int i, j;
+ trailSystem_t *ts = NULL;
+ baseTrailSystem_t *bts = &baseTrailSystems[ psHandle - 1 ];
+
+ if( !bts->registered )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a trail system has not been registered yet\n" );
+ return NULL;
+ }
+
+ for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ )
+ {
+ ts = &trailSystems[ i ];
+
+ if( !ts->valid )
+ {
+ memset( ts, 0, sizeof( trailSystem_t ) );
+
+ //found a free slot
+ ts->class = bts;
+
+ ts->valid = qtrue;
+ ts->destroyTime = -1;
+
+ for( j = 0; j < bts->numBeams; j++ )
+ CG_SpawnNewTrailBeam( bts->beams[ j ], ts );
+
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "TS %s created\n", bts->name );
+
+ break;
+ }
+ }
+
+ return ts;
+}
+
+/*
+===============
+CG_DestroyTrailSystem
+
+Destroy a trail system
+===============
+*/
+void CG_DestroyTrailSystem( trailSystem_t **ts )
+{
+ (*ts)->destroyTime = cg.time;
+
+ if( CG_Attached( &(*ts)->frontAttachment ) &&
+ !CG_Attached( &(*ts)->backAttachment ) )
+ {
+ vec3_t v;
+
+ // attach the trail head to a static point
+ CG_AttachmentPoint( &(*ts)->frontAttachment, v );
+ CG_SetAttachmentPoint( &(*ts)->frontAttachment, v );
+ CG_AttachToPoint( &(*ts)->frontAttachment );
+
+ (*ts)->frontAttachment.centValid = qfalse; // a bit naughty
+ }
+
+ ts = NULL;
+}
+
+/*
+===============
+CG_IsTrailSystemValid
+
+Test a trail system for validity
+===============
+*/
+qboolean CG_IsTrailSystemValid( trailSystem_t **ts )
+{
+ if( *ts == NULL || ( *ts && !(*ts)->valid ) )
+ {
+ if( *ts && !(*ts)->valid )
+ *ts = NULL;
+
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_GarbageCollectTrailSystems
+
+Destroy inactive trail systems
+===============
+*/
+static void CG_GarbageCollectTrailSystems( void )
+{
+ int i, j, count;
+ trailSystem_t *ts;
+ trailBeam_t *tb;
+ int centNum;
+
+ for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ )
+ {
+ ts = &trailSystems[ i ];
+ count = 0;
+
+ //don't bother checking already invalid systems
+ if( !ts->valid )
+ continue;
+
+ for( j = 0; j < MAX_TRAIL_BEAMS; j++ )
+ {
+ tb = &trailBeams[ j ];
+
+ if( tb->valid && tb->parent == ts )
+ count++;
+ }
+
+ if( !count )
+ ts->valid = qfalse;
+
+ //check systems where the parent cent has left the PVS
+ //( local player entity is always valid )
+ if( ( centNum = CG_AttachmentCentNum( &ts->frontAttachment ) ) >= 0 &&
+ centNum != cg.snap->ps.clientNum )
+ {
+ trailSystem_t *tempTS = ts;
+
+ if( !cg_entities[ centNum ].valid )
+ CG_DestroyTrailSystem( &tempTS );
+ }
+
+ if( ( centNum = CG_AttachmentCentNum( &ts->backAttachment ) ) >= 0 &&
+ centNum != cg.snap->ps.clientNum )
+ {
+ trailSystem_t *tempTS = ts;
+
+ if( !cg_entities[ centNum ].valid )
+ CG_DestroyTrailSystem( &tempTS );
+ }
+
+ if( cg_debugTrails.integer >= 1 && !ts->valid )
+ CG_Printf( "TS %s garbage collected\n", ts->class->name );
+ }
+}
+
+/*
+===============
+CG_AddTrails
+
+Add trails to the scene
+===============
+*/
+void CG_AddTrails( void )
+{
+ int i;
+ trailBeam_t *tb;
+ int numTS = 0, numTB = 0;
+
+ //remove expired trail systems
+ CG_GarbageCollectTrailSystems( );
+
+ for( i = 0; i < MAX_TRAIL_BEAMS; i++ )
+ {
+ tb = &trailBeams[ i ];
+
+ if( tb->valid )
+ {
+ CG_UpdateBeam( tb );
+ CG_RenderBeam( tb );
+ }
+ }
+
+ if( cg_debugTrails.integer >= 2 )
+ {
+ for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ )
+ if( trailSystems[ i ].valid )
+ numTS++;
+
+ for( i = 0; i < MAX_TRAIL_BEAMS; i++ )
+ if( trailBeams[ i ].valid )
+ numTB++;
+
+ CG_Printf( "TS: %d TB: %d\n", numTS, numTB );
+ }
+}
+
+static trailSystem_t *testTS;
+static qhandle_t testTSHandle;
+
+/*
+===============
+CG_DestroyTestTS_f
+
+Destroy the test a trail system
+===============
+*/
+void CG_DestroyTestTS_f( void )
+{
+ if( CG_IsTrailSystemValid( &testTS ) )
+ CG_DestroyTrailSystem( &testTS );
+}
+
+/*
+===============
+CG_TestTS_f
+
+Test a trail system
+===============
+*/
+void CG_TestTS_f( void )
+{
+ char tsName[ MAX_QPATH ];
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ Q_strncpyz( tsName, CG_Argv( 1 ), MAX_QPATH );
+ testTSHandle = CG_RegisterTrailSystem( tsName );
+
+ if( testTSHandle )
+ {
+ CG_DestroyTestTS_f( );
+
+ testTS = CG_SpawnNewTrailSystem( testTSHandle );
+
+ if( CG_IsTrailSystemValid( &testTS ) )
+ {
+ CG_SetAttachmentCent( &testTS->frontAttachment, &cg_entities[ 0 ] );
+ CG_AttachToCent( &testTS->frontAttachment );
+ }
+ }
+}
diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c
new file mode 100644
index 0000000..c418726
--- /dev/null
+++ b/src/cgame/cg_tutorial.c
@@ -0,0 +1,664 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_tutorial.c -- the tutorial system
+
+#include "cg_local.h"
+
+typedef struct
+{
+ char *command;
+ char *humanName;
+ keyNum_t keys[ 2 ];
+} bind_t;
+
+static bind_t bindings[ ] =
+{
+ { "+button2", "Activate Upgrade", { -1, -1 } },
+ { "+speed", "Run/Walk", { -1, -1 } },
+ { "boost", "Sprint", { -1, -1 } },
+ { "+moveup", "Jump", { -1, -1 } },
+ { "+movedown", "Crouch", { -1, -1 } },
+ { "+attack", "Primary Attack", { -1, -1 } },
+ { "+button5", "Secondary Attack", { -1, -1 } },
+ { "reload", "Reload", { -1, -1 } },
+ { "buy ammo", "Buy Ammo", { -1, -1 } },
+ { "itemact medkit", "Use Medkit", { -1, -1 } },
+ { "+button7", "Use Structure/Evolve", { -1, -1 } },
+ { "deconstruct", "Deconstruct Structure", { -1, -1 } },
+ { "weapprev", "Previous Upgrade", { -1, -1 } },
+ { "weapnext", "Next Upgrade", { -1, -1 } }
+};
+
+static const int numBindings = sizeof( bindings ) / sizeof( bind_t );
+
+/*
+=================
+CG_GetBindings
+=================
+*/
+static void CG_GetBindings( void )
+{
+ int i, j, numKeys;
+ char buffer[ MAX_STRING_CHARS ];
+
+ for( i = 0; i < numBindings; i++ )
+ {
+ bindings[ i ].keys[ 0 ] = bindings[ i ].keys[ 1 ] = K_NONE;
+ numKeys = 0;
+
+ for( j = 0; j < K_LAST_KEY; j++ )
+ {
+ trap_Key_GetBindingBuf( j, buffer, MAX_STRING_CHARS );
+
+ if( buffer[ 0 ] == 0 )
+ continue;
+
+ if( !Q_stricmp( buffer, bindings[ i ].command ) )
+ {
+ bindings[ i ].keys[ numKeys++ ] = j;
+
+ if( numKeys > 1 )
+ break;
+ }
+ }
+ }
+}
+
+/*
+===============
+CG_KeyNameForCommand
+===============
+*/
+static const char *CG_KeyNameForCommand( const char *command )
+{
+ int i, j;
+ static char buffer[ MAX_STRING_CHARS ];
+ int firstKeyLength;
+
+ buffer[ 0 ] = '\0';
+
+ for( i = 0; i < numBindings; i++ )
+ {
+ if( !Q_stricmp( command, bindings[ i ].command ) )
+ {
+ if( bindings[ i ].keys[ 0 ] != K_NONE )
+ {
+ trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 0 ],
+ buffer, MAX_STRING_CHARS );
+ firstKeyLength = strlen( buffer );
+
+ for( j = 0; j < firstKeyLength; j++ )
+ buffer[ j ] = toupper( buffer[ j ] );
+
+ if( bindings[ i ].keys[ 1 ] != K_NONE )
+ {
+ Q_strcat( buffer, MAX_STRING_CHARS, " or " );
+ trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 1 ],
+ buffer + strlen( buffer ), MAX_STRING_CHARS - strlen( buffer ) );
+
+ for( j = firstKeyLength + 4; j < strlen( buffer ); j++ )
+ buffer[ j ] = toupper( buffer[ j ] );
+ }
+ }
+ else
+ {
+ Q_strncpyz( buffer, va( "\"%s\"", bindings[ i ].humanName ),
+ MAX_STRING_CHARS );
+ Q_strcat( buffer, MAX_STRING_CHARS, " (unbound)" );
+ }
+
+ return buffer;
+ }
+ }
+
+ return "";
+}
+
+#define MAX_TUTORIAL_TEXT 4096
+
+/*
+===============
+CG_BuildableInRange
+===============
+*/
+static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFraction )
+{
+ vec3_t view, point;
+ trace_t trace;
+ entityState_t *es;
+ int health;
+
+ AngleVectors( cg.refdefViewAngles, view, NULL, NULL );
+ VectorMA( cg.refdef.vieworg, 64, view, point );
+ CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL,
+ point, ps->clientNum, MASK_SHOT );
+
+ es = &cg_entities[ trace.entityNum ].currentState;
+
+ if( healthFraction )
+ {
+ health = es->generic1 & B_HEALTH_MASK;
+ *healthFraction = (float)health / B_HEALTH_MASK;
+ }
+
+ if( es->eType == ET_BUILDABLE &&
+ ps->stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) )
+ return es;
+ else
+ return NULL;
+}
+
+/*
+===============
+CG_AlienBuilderText
+===============
+*/
+static void CG_AlienBuilderText( char *text, playerState_t *ps )
+{
+ buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT;
+ entityState_t *es;
+
+ if( buildable > BA_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to place the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to cancel placing the %s\n",
+ CG_KeyNameForCommand( "+button5" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to build a structure\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ( es = CG_BuildableInRange( ps, NULL ) ) )
+ {
+ if( cgs.markDeconstruct )
+ {
+ if( es->generic1 & B_MARKED_TOGGLEBIT )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to unmark this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to mark this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to destroy this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ }
+ }
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG )
+ {
+ if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to swipe\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to launch a projectile\n",
+ CG_KeyNameForCommand( "+button2" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to walk on walls\n",
+ CG_KeyNameForCommand( "+movedown" ) ) );
+ }
+}
+
+/*
+===============
+CG_AlienLevel0Text
+===============
+*/
+static void CG_AlienLevel0Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ "Touch a human to damage it\n" );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to walk on walls\n",
+ CG_KeyNameForCommand( "+movedown" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel1Text
+===============
+*/
+static void CG_AlienLevel1Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ "Touch a human to grab it\n" );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to swipe\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to spray poisonous gas\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to walk on walls\n",
+ CG_KeyNameForCommand( "+movedown" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel2Text
+===============
+*/
+static void CG_AlienLevel2Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to bite\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL2_UPG )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to invoke an electrical attack\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold down %s then touch a wall to wall jump\n",
+ CG_KeyNameForCommand( "+moveup" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel3Text
+===============
+*/
+static void CG_AlienLevel3Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to bite\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL3_UPG )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to launch a projectile\n",
+ CG_KeyNameForCommand( "+button2" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold down and release %s to pounce\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel4Text
+===============
+*/
+static void CG_AlienLevel4Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to swipe\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold down and release %s to charge\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+}
+
+/*
+===============
+CG_HumanCkitText
+===============
+*/
+static void CG_HumanCkitText( char *text, playerState_t *ps )
+{
+ buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT;
+ float health;
+
+ if( buildable > BA_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to place the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to cancel placing the %s\n",
+ CG_KeyNameForCommand( "+button5" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to build a structure\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( CG_BuildableInRange( ps, &health ) )
+ {
+ if( health < 1.0f )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold %s to repair this structure\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to destroy this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ }
+}
+
+/*
+===============
+CG_HumanText
+===============
+*/
+static void CG_HumanText( char *text, playerState_t *ps )
+{
+ char *name;
+ int ammo, clips;
+ upgrade_t upgrade = UP_NONE;
+
+ if( cg.weaponSelect <= 32 )
+ name = cg_weapons[ cg.weaponSelect ].humanName;
+ else if( cg.weaponSelect > 32 )
+ {
+ name = cg_upgrades[ cg.weaponSelect - 32 ].humanName;
+ upgrade = cg.weaponSelect - 32;
+ }
+
+ ammo = ps->ammo;
+ clips = ps->clips;
+
+ if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) )
+ {
+ //no ammo
+ switch( ps->weapon )
+ {
+ case WP_MACHINEGUN:
+ case WP_CHAINGUN:
+ case WP_SHOTGUN:
+ case WP_FLAMER:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Find an Armoury and press %s for more ammo\n",
+ CG_KeyNameForCommand( "buy ammo" ) ) );
+ break;
+
+ case WP_LAS_GUN:
+ case WP_PULSE_RIFLE:
+ case WP_MASS_DRIVER:
+ case WP_LUCIFER_CANNON:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Find a Reactor or Repeater and press %s for more ammo\n",
+ CG_KeyNameForCommand( "buy ammo" ) ) );
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ switch( ps->weapon )
+ {
+ case WP_BLASTER:
+ case WP_MACHINEGUN:
+ case WP_SHOTGUN:
+ case WP_LAS_GUN:
+ case WP_CHAINGUN:
+ case WP_PULSE_RIFLE:
+ case WP_FLAMER:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to fire the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+ break;
+
+ case WP_MASS_DRIVER:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to fire the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold %s to zoom\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ break;
+
+ case WP_PAIN_SAW:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold %s to activate the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+ break;
+
+ case WP_LUCIFER_CANNON:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold and release %s to fire a charged shot\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to fire the %s\n",
+ CG_KeyNameForCommand( "+button5" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+ break;
+
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ CG_HumanCkitText( text, ps );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s and ",
+ CG_KeyNameForCommand( "weapprev" ) ) );
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "%s to select an upgrade\n",
+ CG_KeyNameForCommand( "weapnext" ) ) );
+
+ if( upgrade == UP_NONE ||
+ ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to use the %s\n",
+ CG_KeyNameForCommand( "+button2" ),
+ name ) );
+ }
+
+ if( ps->stats[ STAT_HEALTH ] <= 35 &&
+ BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to use your %s\n",
+ CG_KeyNameForCommand( "itemact medkit" ),
+ BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to use a structure\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to sprint\n",
+ CG_KeyNameForCommand( "boost" ) ) );
+}
+
+/*
+===============
+CG_SpectatorText
+===============
+*/
+static void CG_SpectatorText( char *text, playerState_t *ps )
+{
+ if( cgs.clientinfo[ cg.clientNum ].team != PTE_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to spawn\n", CG_KeyNameForCommand( "+attack" ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to join a team\n", CG_KeyNameForCommand( "+attack" ) ) );
+ }
+
+ if( ps->pm_flags & PMF_FOLLOW )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to stop following\n",
+ CG_KeyNameForCommand( "+button2" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s or ", CG_KeyNameForCommand( "weapprev" ) ) );
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "%s to change player\n", CG_KeyNameForCommand( "weapnext" ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to follow a %s\n", CG_KeyNameForCommand( "+button2" ),
+ ( cgs.clientinfo[ cg.clientNum ].team == PTE_NONE )
+ ? "player" : "teammate" ) );
+ }
+}
+
+/*
+===============
+CG_TutorialText
+
+Returns context help for the current class/weapon
+===============
+*/
+const char *CG_TutorialText( void )
+{
+ playerState_t *ps;
+ static char text[ MAX_TUTORIAL_TEXT ];
+
+ CG_GetBindings( );
+
+ text[ 0 ] = '\0';
+ ps = &cg.snap->ps;
+
+ if( !cg.intermissionStarted && !cg.demoPlayback )
+ {
+ if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ||
+ ps->pm_flags & PMF_FOLLOW )
+ {
+ CG_SpectatorText( text, ps );
+ }
+ else if( ps->stats[ STAT_HEALTH ] > 0 )
+ {
+ switch( ps->stats[ STAT_PCLASS ] )
+ {
+ case PCL_ALIEN_BUILDER0:
+ case PCL_ALIEN_BUILDER0_UPG:
+ CG_AlienBuilderText( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL0:
+ CG_AlienLevel0Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL1:
+ case PCL_ALIEN_LEVEL1_UPG:
+ CG_AlienLevel1Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL2:
+ case PCL_ALIEN_LEVEL2_UPG:
+ CG_AlienLevel2Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL3:
+ case PCL_ALIEN_LEVEL3_UPG:
+ CG_AlienLevel3Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL4:
+ CG_AlienLevel4Text( text, ps );
+ break;
+
+ case PCL_HUMAN:
+ CG_HumanText( text, ps );
+ break;
+
+ default:
+ break;
+ }
+
+ if( ps->stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ entityState_t *es = CG_BuildableInRange( ps, NULL );
+
+ if( ps->stats[ STAT_STATE ] & SS_HOVELING )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to exit the hovel\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+ }
+ else if( es && es->modelindex == BA_A_HOVEL &&
+ es->generic1 & B_SPAWNED_TOGGLEBIT &&
+ ( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ||
+ ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to enter the hovel\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+ }
+ else if( BG_UpgradeClassAvailable( ps ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to evolve\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+ }
+ }
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" );
+ }
+
+ return text;
+}
diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c
new file mode 100644
index 0000000..428f299
--- /dev/null
+++ b/src/cgame/cg_view.c
@@ -0,0 +1,1338 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_view.c -- setup all the parameters (position, angle, etc)
+// for a 3D rendering
+
+
+#include "cg_local.h"
+
+
+/*
+=============================================================================
+
+ MODEL TESTING
+
+The viewthing and gun positioning tools from Q2 have been integrated and
+enhanced into a single model testing facility.
+
+Model viewing can begin with either "testmodel <modelname>" or "testgun <modelname>".
+
+The names must be the full pathname after the basedir, like
+"models/weapons/v_launch/tris.md3" or "players/male/tris.md3"
+
+Testmodel will create a fake entity 100 units in front of the current view
+position, directly facing the viewer. It will remain immobile, so you can
+move around it to view it from different angles.
+
+Testgun will cause the model to follow the player around and supress the real
+view weapon model. The default frame 0 of most guns is completely off screen,
+so you will probably have to cycle a couple frames to see it.
+
+"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the
+frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in
+q3default.cfg.
+
+If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let
+you adjust the positioning.
+
+Note that none of the model testing features update while the game is paused, so
+it may be convenient to test with deathmatch set to 1 so that bringing down the
+console doesn't pause the game.
+
+=============================================================================
+*/
+
+/*
+=================
+CG_TestModel_f
+
+Creates an entity in front of the current position, which
+can then be moved around
+=================
+*/
+void CG_TestModel_f( void )
+{
+ vec3_t angles;
+
+ memset( &cg.testModelEntity, 0, sizeof( cg.testModelEntity ) );
+ memset( &cg.testModelBarrelEntity, 0, sizeof( cg.testModelBarrelEntity ) );
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ Q_strncpyz( cg.testModelName, CG_Argv( 1 ), MAX_QPATH );
+ cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
+
+ Q_strncpyz( cg.testModelBarrelName, CG_Argv( 1 ), MAX_QPATH );
+ cg.testModelBarrelName[ strlen( cg.testModelBarrelName ) - 4 ] = '\0';
+ Q_strcat( cg.testModelBarrelName, MAX_QPATH, "_barrel.md3" );
+ cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName );
+
+ if( trap_Argc( ) == 3 )
+ {
+ cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) );
+ cg.testModelEntity.frame = 1;
+ cg.testModelEntity.oldframe = 0;
+ }
+
+ if( !cg.testModelEntity.hModel )
+ {
+ CG_Printf( "Can't register model\n" );
+ return;
+ }
+
+ VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], cg.testModelEntity.origin );
+
+ angles[ PITCH ] = 0;
+ angles[ YAW ] = 180 + cg.refdefViewAngles[ 1 ];
+ angles[ ROLL ] = 0;
+
+ AnglesToAxis( angles, cg.testModelEntity.axis );
+ cg.testGun = qfalse;
+
+ if( cg.testModelBarrelEntity.hModel )
+ {
+ angles[ YAW ] = 0;
+ angles[ PITCH ] = 0;
+ angles[ ROLL ] = 0;
+ AnglesToAxis( angles, cg.testModelBarrelEntity.axis );
+ }
+}
+
+/*
+=================
+CG_TestGun_f
+
+Replaces the current view weapon with the given model
+=================
+*/
+void CG_TestGun_f( void )
+{
+ CG_TestModel_f( );
+ cg.testGun = qtrue;
+ cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON;
+}
+
+
+void CG_TestModelNextFrame_f( void )
+{
+ cg.testModelEntity.frame++;
+ CG_Printf( "frame %i\n", cg.testModelEntity.frame );
+}
+
+void CG_TestModelPrevFrame_f( void )
+{
+ cg.testModelEntity.frame--;
+
+ if( cg.testModelEntity.frame < 0 )
+ cg.testModelEntity.frame = 0;
+
+ CG_Printf( "frame %i\n", cg.testModelEntity.frame );
+}
+
+void CG_TestModelNextSkin_f( void )
+{
+ cg.testModelEntity.skinNum++;
+ CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
+}
+
+void CG_TestModelPrevSkin_f( void )
+{
+ cg.testModelEntity.skinNum--;
+
+ if( cg.testModelEntity.skinNum < 0 )
+ cg.testModelEntity.skinNum = 0;
+
+ CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
+}
+
+static void CG_AddTestModel( void )
+{
+ int i;
+
+ // re-register the model, because the level may have changed
+ cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
+ cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName );
+
+ if( !cg.testModelEntity.hModel )
+ {
+ CG_Printf( "Can't register model\n" );
+ return;
+ }
+
+ // if testing a gun, set the origin reletive to the view origin
+ if( cg.testGun )
+ {
+ VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin );
+ VectorCopy( cg.refdef.viewaxis[ 0 ], cg.testModelEntity.axis[ 0 ] );
+ VectorCopy( cg.refdef.viewaxis[ 1 ], cg.testModelEntity.axis[ 1 ] );
+ VectorCopy( cg.refdef.viewaxis[ 2 ], cg.testModelEntity.axis[ 2 ] );
+
+ // allow the position to be adjusted
+ for( i = 0; i < 3; i++ )
+ {
+ cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 0 ][ i ] * cg_gun_x.value;
+ cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 1 ][ i ] * cg_gun_y.value;
+ cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 2 ][ i ] * cg_gun_z.value;
+ }
+ }
+
+ trap_R_AddRefEntityToScene( &cg.testModelEntity );
+
+ if( cg.testModelBarrelEntity.hModel )
+ {
+ CG_PositionEntityOnTag( &cg.testModelBarrelEntity, &cg.testModelEntity,
+ cg.testModelEntity.hModel, "tag_barrel" );
+
+ trap_R_AddRefEntityToScene( &cg.testModelBarrelEntity );
+ }
+}
+
+
+
+//============================================================================
+
+
+/*
+=================
+CG_CalcVrect
+
+Sets the coordinates of the rendered window
+=================
+*/
+static void CG_CalcVrect( void )
+{
+ int size;
+
+ // the intermission should allways be full screen
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ size = 100;
+ else
+ {
+ // bound normal viewsize
+ if( cg_viewsize.integer < 30 )
+ {
+ trap_Cvar_Set( "cg_viewsize", "30" );
+ size = 30;
+ }
+ else if( cg_viewsize.integer > 100 )
+ {
+ trap_Cvar_Set( "cg_viewsize","100" );
+ size = 100;
+ }
+ else
+ size = cg_viewsize.integer;
+ }
+
+ cg.refdef.width = cgs.glconfig.vidWidth * size / 100;
+ cg.refdef.width &= ~1;
+
+ cg.refdef.height = cgs.glconfig.vidHeight * size / 100;
+ cg.refdef.height &= ~1;
+
+ cg.refdef.x = ( cgs.glconfig.vidWidth - cg.refdef.width ) / 2;
+ cg.refdef.y = ( cgs.glconfig.vidHeight - cg.refdef.height ) / 2;
+}
+
+//==============================================================================
+
+
+/*
+===============
+CG_OffsetThirdPersonView
+
+===============
+*/
+#define FOCUS_DISTANCE 512
+static void CG_OffsetThirdPersonView( void )
+{
+ vec3_t forward, right, up;
+ vec3_t view;
+ vec3_t focusAngles;
+ trace_t trace;
+ static vec3_t mins = { -8, -8, -8 };
+ static vec3_t maxs = { 8, 8, 8 };
+ vec3_t focusPoint;
+ float focusDist;
+ float forwardScale, sideScale;
+ vec3_t surfNormal;
+
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( surfNormal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal );
+ }
+ else
+ VectorSet( surfNormal, 0.0f, 0.0f, 1.0f );
+
+ VectorMA( cg.refdef.vieworg, cg.predictedPlayerState.viewheight, surfNormal, cg.refdef.vieworg );
+
+ VectorCopy( cg.refdefViewAngles, focusAngles );
+
+ // if dead, look at killer
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ {
+ focusAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ];
+ cg.refdefViewAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ];
+ }
+
+ //if ( focusAngles[PITCH] > 45 ) {
+ // focusAngles[PITCH] = 45; // don't go too far overhead
+ //}
+ AngleVectors( focusAngles, forward, NULL, NULL );
+
+ VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint );
+
+ VectorCopy( cg.refdef.vieworg, view );
+
+ VectorMA( view, 12, surfNormal, view );
+
+ //cg.refdefViewAngles[PITCH] *= 0.5;
+
+ AngleVectors( cg.refdefViewAngles, forward, right, up );
+
+ forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI );
+ sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI );
+ VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view );
+ VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view );
+
+ // trace a ray from the origin to the viewpoint to make sure the view isn't
+ // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything
+
+ if( !cg_cameraMode.integer )
+ {
+ CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
+
+ if( trace.fraction != 1.0 )
+ {
+ VectorCopy( trace.endpos, view );
+ view[ 2 ] += ( 1.0 - trace.fraction ) * 32;
+ // try another trace to this position, because a tunnel may have the ceiling
+ // close enogh that this is poking out
+
+ CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
+ VectorCopy( trace.endpos, view );
+ }
+ }
+
+ VectorCopy( view, cg.refdef.vieworg );
+
+ // select pitch to look at focus point from vieword
+ VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint );
+ focusDist = sqrt( focusPoint[ 0 ] * focusPoint[ 0 ] + focusPoint[ 1 ] * focusPoint[ 1 ] );
+ if ( focusDist < 1 ) {
+ focusDist = 1; // should never happen
+ }
+ cg.refdefViewAngles[ PITCH ] = -180 / M_PI * atan2( focusPoint[ 2 ], focusDist );
+ cg.refdefViewAngles[ YAW ] -= cg_thirdPersonAngle.value;
+}
+
+
+// this causes a compiler bug on mac MrC compiler
+static void CG_StepOffset( void )
+{
+ float steptime;
+ int timeDelta;
+ vec3_t normal;
+ playerState_t *ps = &cg.predictedPlayerState;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( ps->grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+ steptime = BG_FindSteptimeForClass( ps->stats[ STAT_PCLASS ] );
+
+ // smooth out stair climbing
+ timeDelta = cg.time - cg.stepTime;
+ if( timeDelta < steptime )
+ {
+ float stepChange = cg.stepChange
+ * (steptime - timeDelta) / steptime;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg );
+ else
+ cg.refdef.vieworg[ 2 ] -= stepChange;
+ }
+}
+
+#define PCLOUD_ROLL_AMPLITUDE 25.0f
+#define PCLOUD_ROLL_FREQUENCY 0.4f
+#define PCLOUD_ZOOM_AMPLITUDE 15
+#define PCLOUD_ZOOM_FREQUENCY 0.7f
+
+
+/*
+===============
+CG_OffsetFirstPersonView
+
+===============
+*/
+static void CG_OffsetFirstPersonView( void )
+{
+ float *origin;
+ float *angles;
+ float bob;
+ float ratio;
+ float delta;
+ float speed;
+ float f;
+ vec3_t predictedVelocity;
+ int timeDelta;
+ float bob2;
+ vec3_t normal, baseOrigin;
+ playerState_t *ps = &cg.predictedPlayerState;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( ps->grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ return;
+
+ origin = cg.refdef.vieworg;
+ angles = cg.refdefViewAngles;
+
+ VectorCopy( origin, baseOrigin );
+
+ // if dead, fix the angle and don't add any kick
+ if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 )
+ {
+ angles[ ROLL ] = 40;
+ angles[ PITCH ] = -15;
+ angles[ YAW ] = cg.snap->ps.stats[ STAT_VIEWLOCK ];
+ origin[ 2 ] += cg.predictedPlayerState.viewheight;
+ return;
+ }
+
+ // add angles based on weapon kick
+ VectorAdd( angles, cg.kick_angles, angles );
+
+ // add angles based on damage kick
+ if( cg.damageTime )
+ {
+ ratio = cg.time - cg.damageTime;
+ if( ratio < DAMAGE_DEFLECT_TIME )
+ {
+ ratio /= DAMAGE_DEFLECT_TIME;
+ angles[ PITCH ] += ratio * cg.v_dmg_pitch;
+ angles[ ROLL ] += ratio * cg.v_dmg_roll;
+ }
+ else
+ {
+ ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME;
+ if( ratio > 0 )
+ {
+ angles[ PITCH ] += ratio * cg.v_dmg_pitch;
+ angles[ ROLL ] += ratio * cg.v_dmg_roll;
+ }
+ }
+ }
+
+ // add pitch based on fall kick
+#if 0
+ ratio = ( cg.time - cg.landTime) / FALL_TIME;
+ if (ratio < 0)
+ ratio = 0;
+ angles[PITCH] += ratio * cg.fall_value;
+#endif
+
+ // add angles based on velocity
+ VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity );
+
+ delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 0 ] );
+ angles[ PITCH ] += delta * cg_runpitch.value;
+
+ delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 1 ] );
+ angles[ ROLL ] -= delta * cg_runroll.value;
+
+ // add angles based on bob
+ // bob amount is class dependant
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ bob2 = 0.0f;
+ else
+ bob2 = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] );
+
+
+#define LEVEL4_FEEDBACK 10.0f
+
+ //give a charging player some feedback
+ if( ps->weapon == WP_ALEVEL4 )
+ {
+ if( ps->stats[ STAT_MISC ] > 0 )
+ {
+ float fraction = (float)ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME;
+
+ if( fraction > 1.0f )
+ fraction = 1.0f;
+
+ bob2 *= ( 1.0f + fraction * LEVEL4_FEEDBACK );
+ }
+ }
+
+ if( bob2 != 0.0f )
+ {
+ // make sure the bob is visible even at low speeds
+ speed = cg.xyspeed > 200 ? cg.xyspeed : 200;
+
+ delta = cg.bobfracsin * ( bob2 ) * speed;
+ if( cg.predictedPlayerState.pm_flags & PMF_DUCKED )
+ delta *= 3; // crouching
+
+ angles[ PITCH ] += delta;
+ delta = cg.bobfracsin * ( bob2 ) * speed;
+ if( cg.predictedPlayerState.pm_flags & PMF_DUCKED )
+ delta *= 3; // crouching accentuates roll
+
+ if( cg.bobcycle & 1 )
+ delta = -delta;
+
+ angles[ ROLL ] += delta;
+ }
+
+#define LEVEL3_FEEDBACK 20.0f
+
+ //provide some feedback for pouncing
+ if( cg.predictedPlayerState.weapon == WP_ALEVEL3 ||
+ cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG )
+ {
+ if( cg.predictedPlayerState.stats[ STAT_MISC ] > 0 )
+ {
+ float fraction1, fraction2;
+ vec3_t forward;
+
+ AngleVectors( angles, forward, NULL, NULL );
+ VectorNormalize( forward );
+
+ fraction1 = (float)( cg.time - cg.weapon2Time ) / (float)LEVEL3_POUNCE_CHARGE_TIME;
+
+ if( fraction1 > 1.0f )
+ fraction1 = 1.0f;
+
+ fraction2 = -sin( fraction1 * M_PI / 2 );
+
+ VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin );
+ }
+ }
+
+#define STRUGGLE_DIST 5.0f
+#define STRUGGLE_TIME 250
+
+ //allow the player to struggle a little whilst grabbed
+ if( cg.predictedPlayerState.pm_type == PM_GRABBED )
+ {
+ vec3_t forward, right, up;
+ usercmd_t cmd;
+ int cmdNum;
+ float fFraction, rFraction, uFraction;
+ float fFraction2, rFraction2, uFraction2;
+
+ cmdNum = trap_GetCurrentCmdNumber();
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ AngleVectors( angles, forward, right, up );
+
+ fFraction = (float)( cg.time - cg.forwardMoveTime ) / STRUGGLE_TIME;
+ rFraction = (float)( cg.time - cg.rightMoveTime ) / STRUGGLE_TIME;
+ uFraction = (float)( cg.time - cg.upMoveTime ) / STRUGGLE_TIME;
+
+ if( fFraction > 1.0f )
+ fFraction = 1.0f;
+ if( rFraction > 1.0f )
+ rFraction = 1.0f;
+ if( uFraction > 1.0f )
+ uFraction = 1.0f;
+
+ fFraction2 = -sin( fFraction * M_PI / 2 );
+ rFraction2 = -sin( rFraction * M_PI / 2 );
+ uFraction2 = -sin( uFraction * M_PI / 2 );
+
+ if( cmd.forwardmove > 0 )
+ VectorMA( origin, STRUGGLE_DIST * fFraction, forward, origin );
+ else if( cmd.forwardmove < 0 )
+ VectorMA( origin, -STRUGGLE_DIST * fFraction, forward, origin );
+ else
+ cg.forwardMoveTime = cg.time;
+
+ if( cmd.rightmove > 0 )
+ VectorMA( origin, STRUGGLE_DIST * rFraction, right, origin );
+ else if( cmd.rightmove < 0 )
+ VectorMA( origin, -STRUGGLE_DIST * rFraction, right, origin );
+ else
+ cg.rightMoveTime = cg.time;
+
+ if( cmd.upmove > 0 )
+ VectorMA( origin, STRUGGLE_DIST * uFraction, up, origin );
+ else if( cmd.upmove < 0 )
+ VectorMA( origin, -STRUGGLE_DIST * uFraction, up, origin );
+ else
+ cg.upMoveTime = cg.time;
+ }
+
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
+ !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ float fraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 2 * PCLOUD_ROLL_FREQUENCY );
+ float pitchFraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 5 * PCLOUD_ROLL_FREQUENCY );
+
+ fraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME );
+ pitchFraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME );
+
+ angles[ ROLL ] += fraction * PCLOUD_ROLL_AMPLITUDE;
+ angles[ YAW ] += fraction * PCLOUD_ROLL_AMPLITUDE;
+ angles[ PITCH ] += pitchFraction * PCLOUD_ROLL_AMPLITUDE / 2.0f;
+ }
+
+ // this *feels* more realisitic for humans
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ angles[PITCH] += cg.bobfracsin * bob2 * 0.5;
+
+ // heavy breathing effects //FIXME: sound
+ if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 )
+ {
+ float deltaBreath = (float)(
+ cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ?
+ -cg.predictedPlayerState.stats[ STAT_STAMINA ] :
+ cg.predictedPlayerState.stats[ STAT_STAMINA ] ) / 200.0;
+ float deltaAngle = cos( (float)cg.time/150.0 ) * deltaBreath;
+
+ deltaAngle += ( deltaAngle < 0 ? -deltaAngle : deltaAngle ) * 0.5;
+
+ angles[ PITCH ] -= deltaAngle;
+ }
+ }
+
+//===================================
+
+ // add view height
+ // when wall climbing the viewheight is not straight up
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ VectorMA( origin, ps->viewheight, normal, origin );
+ else
+ origin[ 2 ] += cg.predictedPlayerState.viewheight;
+
+ // smooth out duck height changes
+ timeDelta = cg.time - cg.duckTime;
+ if( timeDelta < DUCK_TIME)
+ {
+ cg.refdef.vieworg[ 2 ] -= cg.duckChange
+ * ( DUCK_TIME - timeDelta ) / DUCK_TIME;
+ }
+
+ // add bob height
+ bob = cg.bobfracsin * cg.xyspeed * bob2;
+
+ if( bob > 6 )
+ bob = 6;
+
+ // likewise for bob
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ VectorMA( origin, bob, normal, origin );
+ else
+ origin[ 2 ] += bob;
+
+
+ // add fall height
+ delta = cg.time - cg.landTime;
+
+ if( delta < LAND_DEFLECT_TIME )
+ {
+ f = delta / LAND_DEFLECT_TIME;
+ cg.refdef.vieworg[ 2 ] += cg.landChange * f;
+ }
+ else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME )
+ {
+ delta -= LAND_DEFLECT_TIME;
+ f = 1.0 - ( delta / LAND_RETURN_TIME );
+ cg.refdef.vieworg[ 2 ] += cg.landChange * f;
+ }
+
+ // add step offset
+ CG_StepOffset( );
+
+ // add kick offset
+
+ VectorAdd (origin, cg.kick_origin, origin);
+}
+
+//======================================================================
+
+/*
+====================
+CG_CalcFov
+
+Fixed fov at intermissions, otherwise account for fov variable and zooms.
+====================
+*/
+#define WAVE_AMPLITUDE 1
+#define WAVE_FREQUENCY 0.4
+
+#define FOVWARPTIME 400.0
+
+static int CG_CalcFov( void )
+{
+ float x;
+ float phase;
+ float v;
+ int contents;
+ float fov_x, fov_y;
+ float zoomFov;
+ float f;
+ int inwater;
+ int attribFov;
+ usercmd_t cmd;
+ int cmdNum;
+
+ cmdNum = trap_GetCurrentCmdNumber( );
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ if( cg.predictedPlayerState.pm_type == PM_INTERMISSION ||
+ ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) )
+ {
+ // if in intermission, use a fixed value
+ fov_x = 90;
+ }
+ else
+ {
+ // don't lock the fov globally - we need to be able to change it
+ attribFov = BG_FindFovForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] );
+ fov_x = attribFov;
+
+ if ( fov_x < 1 )
+ fov_x = 1;
+ else if ( fov_x > 160 )
+ fov_x = 160;
+
+ if( cg.spawnTime > ( cg.time - FOVWARPTIME ) &&
+ BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_FOVWARPS ) )
+ {
+ float temp, temp2;
+
+ temp = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME;
+ temp2 = ( 170 - fov_x ) * temp;
+
+ //Com_Printf( "%f %f\n", temp*100, temp2*100 );
+
+ fov_x = 170 - temp2;
+ }
+
+ // account for zooms
+ zoomFov = BG_FindZoomFovForWeapon( cg.predictedPlayerState.weapon );
+ if ( zoomFov < 1 )
+ zoomFov = 1;
+ else if ( zoomFov > attribFov )
+ zoomFov = attribFov;
+
+ // only do all the zoom stuff if the client CAN zoom
+ // FIXME: zoom control is currently hard coded to BUTTON_ATTACK2
+ if( BG_WeaponCanZoom( cg.predictedPlayerState.weapon ) )
+ {
+ if ( cg.zoomed )
+ {
+ f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME;
+
+ if ( f > 1.0 )
+ fov_x = zoomFov;
+ else
+ fov_x = fov_x + f * ( zoomFov - fov_x );
+
+ // BUTTON_ATTACK2 isn't held so unzoom next time
+ if( !( cmd.buttons & BUTTON_ATTACK2 ) )
+ {
+ cg.zoomed = qfalse;
+ cg.zoomTime = cg.time;
+ }
+ }
+ else
+ {
+ f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME;
+
+ if ( f <= 1.0 )
+ fov_x = zoomFov + f * ( fov_x - zoomFov );
+
+ // BUTTON_ATTACK2 is held so zoom next time
+ if( cmd.buttons & BUTTON_ATTACK2 )
+ {
+ cg.zoomed = qtrue;
+ cg.zoomTime = cg.time;
+ }
+ }
+ }
+ }
+
+ x = cg.refdef.width / tan( fov_x / 360 * M_PI );
+ fov_y = atan2( cg.refdef.height, x );
+ fov_y = fov_y * 360 / M_PI;
+
+ // warp if underwater
+ contents = CG_PointContents( cg.refdef.vieworg, -1 );
+
+ if( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
+ {
+ phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
+ v = WAVE_AMPLITUDE * sin( phase );
+ fov_x += v;
+ fov_y -= v;
+ inwater = qtrue;
+ }
+ else
+ inwater = qfalse;
+
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
+ cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 &&
+ !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ phase = cg.time / 1000.0 * PCLOUD_ZOOM_FREQUENCY * M_PI * 2;
+ v = PCLOUD_ZOOM_AMPLITUDE * sin( phase );
+ v *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME );
+ fov_x += v;
+ fov_y += v;
+ }
+
+
+ // set it
+ cg.refdef.fov_x = fov_x;
+ cg.refdef.fov_y = fov_y;
+
+ if( !cg.zoomed )
+ cg.zoomSensitivity = 1;
+ else
+ cg.zoomSensitivity = cg.refdef.fov_y / 75.0;
+
+ return inwater;
+}
+
+
+
+#define NORMAL_HEIGHT 64.0f
+#define NORMAL_WIDTH 6.0f
+
+/*
+===============
+CG_DrawSurfNormal
+
+Draws a vector against
+the surface player is looking at
+===============
+*/
+static void CG_DrawSurfNormal( void )
+{
+ trace_t tr;
+ vec3_t end, temp;
+ polyVert_t normal[ 4 ];
+ vec4_t color = { 0.0f, 255.0f, 0.0f, 128.0f };
+
+ VectorMA( cg.refdef.vieworg, 8192, cg.refdef.viewaxis[ 0 ], end );
+
+ CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, end, cg.predictedPlayerState.clientNum, MASK_SOLID );
+
+ VectorCopy( tr.endpos, normal[ 0 ].xyz );
+ normal[ 0 ].st[ 0 ] = 0;
+ normal[ 0 ].st[ 1 ] = 0;
+ Vector4Copy( color, normal[ 0 ].modulate );
+
+ VectorMA( tr.endpos, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp );
+ VectorCopy( temp, normal[ 1 ].xyz);
+ normal[ 1 ].st[ 0 ] = 0;
+ normal[ 1 ].st[ 1 ] = 1;
+ Vector4Copy( color, normal[ 1 ].modulate );
+
+ VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp );
+ VectorMA( temp, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp );
+ VectorCopy( temp, normal[ 2 ].xyz );
+ normal[ 2 ].st[ 0 ] = 1;
+ normal[ 2 ].st[ 1 ] = 1;
+ Vector4Copy( color, normal[ 2 ].modulate );
+
+ VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp );
+ VectorCopy( temp, normal[ 3 ].xyz );
+ normal[ 3 ].st[ 0 ] = 1;
+ normal[ 3 ].st[ 1 ] = 0;
+ Vector4Copy( color, normal[ 3 ].modulate );
+
+ trap_R_AddPolyToScene( cgs.media.outlineShader, 4, normal );
+}
+
+/*
+===============
+CG_addSmoothOp
+===============
+*/
+void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod )
+{
+ int i;
+
+ //iterate through smooth array
+ for( i = 0; i < MAXSMOOTHS; i++ )
+ {
+ //found an unused index in the smooth array
+ if( cg.sList[ i ].time + cg_wwSmoothTime.integer < cg.time )
+ {
+ //copy to array and stop
+ VectorCopy( rotAxis, cg.sList[ i ].rotAxis );
+ cg.sList[ i ].rotAngle = rotAngle;
+ cg.sList[ i ].time = cg.time;
+ cg.sList[ i ].timeMod = timeMod;
+ return;
+ }
+ }
+
+ //no free indices in the smooth array
+}
+
+/*
+===============
+CG_smoothWWTransitions
+===============
+*/
+static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t out )
+{
+ vec3_t surfNormal, rotAxis, temp;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+ int i;
+ float stLocal, sFraction, rotAngle;
+ float smoothTime, timeMod;
+ qboolean performed = qfalse;
+ vec3_t inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ];
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ //set surfNormal
+ if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) )
+ VectorCopy( ps->grapplePoint, surfNormal );
+ else
+ VectorCopy( ceilingNormal, surfNormal );
+
+ AnglesToAxis( in, inAxis );
+
+ //if we are moving from one surface to another smooth the transition
+ if( !VectorCompare( surfNormal, cg.lastNormal ) )
+ {
+ //if we moving from the ceiling to the floor special case
+ //( x product of colinear vectors is undefined)
+ if( VectorCompare( ceilingNormal, cg.lastNormal ) &&
+ VectorCompare( refNormal, surfNormal ) )
+ {
+ AngleVectors( in, temp, NULL, NULL );
+ ProjectPointOnPlane( rotAxis, temp, refNormal );
+ VectorNormalize( rotAxis );
+ rotAngle = 180.0f;
+ timeMod = 1.5f;
+ }
+ else
+ {
+ AnglesToAxis( cg.lastVangles, lastAxis );
+ rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) +
+ DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) +
+ DotProduct( inAxis[ 2 ], lastAxis[ 2 ] );
+
+ rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) );
+
+ CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp );
+ VectorCopy( temp, rotAxis );
+ CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+ CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+
+ VectorNormalize( rotAxis );
+
+ timeMod = 1.0f;
+ }
+
+ //add the op
+ CG_addSmoothOp( rotAxis, rotAngle, timeMod );
+ }
+
+ //iterate through ops
+ for( i = MAXSMOOTHS - 1; i >= 0; i-- )
+ {
+ smoothTime = (int)( cg_wwSmoothTime.integer * cg.sList[ i ].timeMod );
+
+ //if this op has time remaining, perform it
+ if( cg.time < cg.sList[ i ].time + smoothTime )
+ {
+ stLocal = 1.0f - ( ( ( cg.sList[ i ].time + smoothTime ) - cg.time ) / smoothTime );
+ sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f;
+
+ RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis,
+ inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis,
+ inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis,
+ inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle );
+
+ AxisCopy( outAxis, inAxis );
+ performed = qtrue;
+ }
+ }
+
+ //if we performed any ops then return the smoothed angles
+ //otherwise simply return the in angles
+ if( performed )
+ AxisToAngles( outAxis, out );
+ else
+ VectorCopy( in, out );
+
+ //copy the current normal to the lastNormal
+ VectorCopy( in, cg.lastVangles );
+ VectorCopy( surfNormal, cg.lastNormal );
+}
+
+/*
+===============
+CG_smoothWJTransitions
+===============
+*/
+static void CG_smoothWJTransitions( playerState_t *ps, const vec3_t in, vec3_t out )
+{
+ int i;
+ float stLocal, sFraction;
+ qboolean performed = qfalse;
+ vec3_t inAxis[ 3 ], outAxis[ 3 ];
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ AnglesToAxis( in, inAxis );
+
+ //iterate through ops
+ for( i = MAXSMOOTHS - 1; i >= 0; i-- )
+ {
+ //if this op has time remaining, perform it
+ if( cg.time < cg.sList[ i ].time + cg_wwSmoothTime.integer )
+ {
+ stLocal = ( ( cg.sList[ i ].time + cg_wwSmoothTime.integer ) - cg.time ) / cg_wwSmoothTime.integer;
+ sFraction = 1.0f - ( ( cos( stLocal * M_PI * 2.0f ) + 1.0f ) / 2.0f );
+
+ RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis,
+ inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis,
+ inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis,
+ inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle );
+
+ AxisCopy( outAxis, inAxis );
+ performed = qtrue;
+ }
+ }
+
+ //if we performed any ops then return the smoothed angles
+ //otherwise simply return the in angles
+ if( performed )
+ AxisToAngles( outAxis, out );
+ else
+ VectorCopy( in, out );
+}
+
+
+/*
+===============
+CG_CalcViewValues
+
+Sets cg.refdef view values
+===============
+*/
+static int CG_CalcViewValues( void )
+{
+ playerState_t *ps;
+
+ memset( &cg.refdef, 0, sizeof( cg.refdef ) );
+
+ // calculate size of 3D view
+ CG_CalcVrect( );
+
+ ps = &cg.predictedPlayerState;
+
+ // intermission view
+ if( ps->pm_type == PM_INTERMISSION )
+ {
+ VectorCopy( ps->origin, cg.refdef.vieworg );
+ VectorCopy( ps->viewangles, cg.refdefViewAngles );
+ AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
+
+ return CG_CalcFov( );
+ }
+
+ cg.bobcycle = ( ps->bobCycle & 128 ) >> 7;
+ cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) );
+ cg.xyspeed = sqrt( ps->velocity[ 0 ] * ps->velocity[ 0 ] +
+ ps->velocity[ 1 ] * ps->velocity[ 1 ] );
+
+ VectorCopy( ps->origin, cg.refdef.vieworg );
+
+ if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) )
+ CG_smoothWWTransitions( ps, ps->viewangles, cg.refdefViewAngles );
+ else if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ CG_smoothWJTransitions( ps, ps->viewangles, cg.refdefViewAngles );
+ else
+ VectorCopy( ps->viewangles, cg.refdefViewAngles );
+
+ //clumsy logic, but it needs to be this way round because the CS propogation
+ //delay screws things up otherwise
+ if( !BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ {
+ if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) )
+ VectorSet( cg.lastNormal, 0.0f, 0.0f, 1.0f );
+ }
+
+ // add error decay
+ if( cg_errorDecay.value > 0 )
+ {
+ int t;
+ float f;
+
+ t = cg.time - cg.predictedErrorTime;
+ f = ( cg_errorDecay.value - t ) / cg_errorDecay.value;
+
+ if( f > 0 && f < 1 )
+ VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg );
+ else
+ cg.predictedErrorTime = 0;
+ }
+
+ //shut off the poison cloud effect if it's still on the go
+ if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 )
+ {
+ if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) )
+ CG_DestroyParticleSystem( &cg.poisonCloudPS );
+ }
+
+ if( cg.renderingThirdPerson )
+ {
+ // back away from character
+ CG_OffsetThirdPersonView( );
+ }
+ else
+ {
+ // offset for local bobbing and kicks
+ CG_OffsetFirstPersonView( );
+ }
+
+ // position eye reletive to origin
+ AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
+
+ if( cg.hyperspace )
+ cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE;
+
+ //draw the surface normal looking at
+ if( cg_drawSurfNormal.integer )
+ CG_DrawSurfNormal( );
+
+ // field of view
+ return CG_CalcFov( );
+}
+
+/*
+=====================
+CG_AddBufferedSound
+=====================
+*/
+void CG_AddBufferedSound( sfxHandle_t sfx )
+{
+ if( !sfx )
+ return;
+
+ cg.soundBuffer[ cg.soundBufferIn ] = sfx;
+ cg.soundBufferIn = ( cg.soundBufferIn + 1 ) % MAX_SOUNDBUFFER;
+
+ if( cg.soundBufferIn == cg.soundBufferOut )
+ cg.soundBufferOut++;
+}
+
+/*
+=====================
+CG_PlayBufferedSounds
+=====================
+*/
+static void CG_PlayBufferedSounds( void )
+{
+ if( cg.soundTime < cg.time )
+ {
+ if( cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[ cg.soundBufferOut ] )
+ {
+ trap_S_StartLocalSound( cg.soundBuffer[ cg.soundBufferOut ], CHAN_ANNOUNCER );
+ cg.soundBuffer[ cg.soundBufferOut ] = 0;
+ cg.soundBufferOut = ( cg.soundBufferOut + 1 ) % MAX_SOUNDBUFFER;
+ cg.soundTime = cg.time + 750;
+ }
+ }
+}
+
+//=========================================================================
+
+/*
+=================
+CG_DrawActiveFrame
+
+Generates and draws a game scene and status information at the given time.
+=================
+*/
+void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback )
+{
+ int inwater;
+
+ cg.time = serverTime;
+ cg.demoPlayback = demoPlayback;
+
+ // update cvars
+ CG_UpdateCvars( );
+
+ // if we are only updating the screen as a loading
+ // pacifier, don't even try to read snapshots
+ if( cg.infoScreenText[ 0 ] != 0 )
+ {
+ CG_DrawLoadingScreen( );
+ return;
+ }
+
+ // any looped sounds will be respecified as entities
+ // are added to the render list
+ trap_S_ClearLoopingSounds( qfalse );
+
+ // clear all the render lists
+ trap_R_ClearScene( );
+
+ // set up cg.snap and possibly cg.nextSnap
+ CG_ProcessSnapshots( );
+
+ // if we haven't received any snapshots yet, all
+ // we can draw is the information screen
+ if( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) )
+ {
+ CG_DrawLoadingScreen( );
+ return;
+ }
+
+ // let the client system know what our weapon and zoom settings are
+ trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity );
+
+ // this counter will be bumped for every valid scene we generate
+ cg.clientFrame++;
+
+ // update cg.predictedPlayerState
+ CG_PredictPlayerState( );
+
+ // decide on third person view
+ cg.renderingThirdPerson = cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 );
+
+ // build cg.refdef
+ inwater = CG_CalcViewValues( );
+
+ // build the render lists
+ if( !cg.hyperspace )
+ {
+ CG_AddPacketEntities( ); // after calcViewValues, so predicted player state is correct
+ CG_AddMarks( );
+ }
+
+ CG_AddViewWeapon( &cg.predictedPlayerState );
+
+ //after CG_AddViewWeapon
+ if( !cg.hyperspace )
+ {
+ CG_AddParticles( );
+ CG_AddTrails( );
+ }
+
+ // add buffered sounds
+ CG_PlayBufferedSounds( );
+
+ // finish up the rest of the refdef
+ if( cg.testModelEntity.hModel )
+ CG_AddTestModel( );
+
+ cg.refdef.time = cg.time;
+ memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) );
+
+ //remove expired console lines
+ if( cg.consoleLines[ 0 ].time + cg_consoleLatency.integer < cg.time && cg_consoleLatency.integer > 0 )
+ CG_RemoveNotifyLine( );
+
+ // update audio positions
+ trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater );
+
+ // make sure the lagometerSample and frame timing isn't done twice when in stereo
+ if( stereoView != STEREO_RIGHT )
+ {
+ cg.frametime = cg.time - cg.oldTime;
+
+ if( cg.frametime < 0 )
+ cg.frametime = 0;
+
+ cg.oldTime = cg.time;
+ CG_AddLagometerFrameInfo( );
+ }
+
+ if( cg_timescale.value != cg_timescaleFadeEnd.value )
+ {
+ if( cg_timescale.value < cg_timescaleFadeEnd.value )
+ {
+ cg_timescale.value += cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000;
+ if( cg_timescale.value > cg_timescaleFadeEnd.value )
+ cg_timescale.value = cg_timescaleFadeEnd.value;
+ }
+ else
+ {
+ cg_timescale.value -= cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000;
+ if( cg_timescale.value < cg_timescaleFadeEnd.value )
+ cg_timescale.value = cg_timescaleFadeEnd.value;
+ }
+
+ if( cg_timescaleFadeSpeed.value )
+ trap_Cvar_Set( "timescale", va( "%f", cg_timescale.value ) );
+ }
+
+ // actually issue the rendering calls
+ CG_DrawActive( stereoView );
+
+ if( cg_stats.integer )
+ CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame );
+}
+
diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c
new file mode 100644
index 0000000..5f15808
--- /dev/null
+++ b/src/cgame/cg_weapons.c
@@ -0,0 +1,1847 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_weapons.c -- events and effects dealing with weapons
+
+
+#include "cg_local.h"
+
+/*
+=================
+CG_RegisterUpgrade
+
+The server says this item is used on this level
+=================
+*/
+void CG_RegisterUpgrade( int upgradeNum )
+{
+ upgradeInfo_t *upgradeInfo;
+ char *icon;
+
+ upgradeInfo = &cg_upgrades[ upgradeNum ];
+
+ if( upgradeNum == 0 )
+ return;
+
+ if( upgradeInfo->registered )
+ return;
+
+ memset( upgradeInfo, 0, sizeof( *upgradeInfo ) );
+ upgradeInfo->registered = qtrue;
+
+ if( !BG_FindNameForUpgrade( upgradeNum ) )
+ CG_Error( "Couldn't find upgrade %i", upgradeNum );
+
+ upgradeInfo->humanName = BG_FindHumanNameForUpgrade( upgradeNum );
+
+ //la la la la la, i'm not listening!
+ if( upgradeNum == UP_GRENADE )
+ upgradeInfo->upgradeIcon = cg_weapons[ WP_GRENADE ].weaponIcon;
+ else if( ( icon = BG_FindIconForUpgrade( upgradeNum ) ) )
+ upgradeInfo->upgradeIcon = trap_R_RegisterShader( icon );
+}
+
+/*
+===============
+CG_InitUpgrades
+
+Precaches upgrades
+===============
+*/
+void CG_InitUpgrades( void )
+{
+ int i;
+
+ memset( cg_upgrades, 0, sizeof( cg_upgrades ) );
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ CG_RegisterUpgrade( i );
+}
+
+
+/*
+===============
+CG_ParseWeaponModeSection
+
+Parse a weapon mode section
+===============
+*/
+static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p )
+{
+ char *token;
+ int i;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "missileModel" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileModel = trap_R_RegisterModel( token );
+
+ if( !wim->missileModel )
+ CG_Printf( S_COLOR_RED "ERROR: missile model not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileSprite" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileSprite = trap_R_RegisterShader( token );
+ wim->missileSpriteSize = size;
+ wim->usesSpriteMissle = qtrue;
+
+ if( !wim->missileSprite )
+ CG_Printf( S_COLOR_RED "ERROR: missile sprite not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileRotates" ) )
+ {
+ wim->missileRotates = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileAnimates" ) )
+ {
+ wim->missileAnimates = qtrue;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimStartFrame = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimNumFrames = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimFrameRate = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimLooping = atoi( token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileParticleSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileParticleSystem = CG_RegisterParticleSystem( token );
+
+ if( !wim->missileParticleSystem )
+ CG_Printf( S_COLOR_RED "ERROR: missile particle system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileTrailSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileTrailSystem = CG_RegisterTrailSystem( token );
+
+ if( !wim->missileTrailSystem )
+ CG_Printf( S_COLOR_RED "ERROR: missile trail system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "muzzleParticleSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->muzzleParticleSystem = CG_RegisterParticleSystem( token );
+
+ if( !wim->muzzleParticleSystem )
+ CG_Printf( S_COLOR_RED "ERROR: muzzle particle system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactParticleSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactParticleSystem = CG_RegisterParticleSystem( token );
+
+ if( !wim->impactParticleSystem )
+ CG_Printf( S_COLOR_RED "ERROR: impact particle system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactMark" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactMark = trap_R_RegisterShader( token );
+ wim->impactMarkSize = size;
+
+ if( !wim->impactMark )
+ CG_Printf( S_COLOR_RED "ERROR: impact mark shader not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactSound" ) )
+ {
+ int index = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ index = atoi( token );
+
+ if( index < 0 )
+ index = 0;
+ else if( index > 3 )
+ index = 3;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactSound[ index ] = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactFleshSound" ) )
+ {
+ int index = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ index = atoi( token );
+
+ if( index < 0 )
+ index = 0;
+ else if( index > 3 )
+ index = 3;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactFleshSound[ index ] = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "alwaysImpact" ) )
+ {
+ wim->alwaysImpact = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "flashDLightColor" ) )
+ {
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->flashDlightColor[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "continuousFlash" ) )
+ {
+ wim->continuousFlash = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileDlightColor" ) )
+ {
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileDlightColor[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileDlight" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ wim->missileDlight = size;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "firingSound" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->firingSound = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileSound" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileSound = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "flashSound" ) )
+ {
+ int index = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ index = atoi( token );
+
+ if( index < 0 )
+ index = 0;
+ else if( index > 3 )
+ index = 3;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->flashSound[ index ] = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this weapon section
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in weapon section\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+
+/*
+======================
+CG_ParseWeaponFile
+
+Parses a configuration file describing a weapon
+======================
+*/
+static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi )
+{
+ char *text_p;
+ int len;
+ char *token;
+ char text[ 20000 ];
+ fileHandle_t f;
+ weaponMode_t weaponMode = WPM_NONE;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( weaponMode == WPM_NONE )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: weapon mode section started without a declaration\n" );
+ return qfalse;
+ }
+ else if( !CG_ParseWeaponModeSection( &wi->wim[ weaponMode ], &text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse weapon mode section\n" );
+ return qfalse;
+ }
+
+ //start parsing ejectors again
+ weaponMode = WPM_NONE;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "primary" ) )
+ {
+ weaponMode = WPM_PRIMARY;
+ continue;
+ }
+ else if( !Q_stricmp( token, "secondary" ) )
+ {
+ weaponMode = WPM_SECONDARY;
+ continue;
+ }
+ else if( !Q_stricmp( token, "tertiary" ) )
+ {
+ weaponMode = WPM_TERTIARY;
+ continue;
+ }
+ else if( !Q_stricmp( token, "weaponModel" ) )
+ {
+ char path[ MAX_QPATH ];
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->weaponModel = trap_R_RegisterModel( token );
+
+ if( !wi->weaponModel )
+ CG_Printf( S_COLOR_RED "ERROR: weapon model not found %s\n", token );
+
+ strcpy( path, token );
+ COM_StripExtension( path, path, MAX_QPATH );
+ strcat( path, "_flash.md3" );
+ wi->flashModel = trap_R_RegisterModel( path );
+
+ strcpy( path, token );
+ COM_StripExtension( path, path, MAX_QPATH );
+ strcat( path, "_barrel.md3" );
+ wi->barrelModel = trap_R_RegisterModel( path );
+
+ strcpy( path, token );
+ COM_StripExtension( path, path, MAX_QPATH );
+ strcat( path, "_hand.md3" );
+ wi->handsModel = trap_R_RegisterModel( path );
+
+ if( !wi->handsModel )
+ wi->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "idleSound" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->readySound = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "icon" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->weaponIcon = wi->ammoIcon = trap_R_RegisterShader( token );
+
+ if( !wi->weaponIcon )
+ CG_Printf( S_COLOR_RED "ERROR: weapon icon not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "crosshair" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->crossHair = trap_R_RegisterShader( token );
+ wi->crossHairSize = size;
+
+ if( !wi->crossHair )
+ CG_Printf( S_COLOR_RED "ERROR: weapon crosshair not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "disableIn3rdPerson" ) )
+ {
+ wi->disableIn3rdPerson = qtrue;
+
+ continue;
+ }
+
+ Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+=================
+CG_RegisterWeapon
+=================
+*/
+void CG_RegisterWeapon( int weaponNum )
+{
+ weaponInfo_t *weaponInfo;
+ char path[ MAX_QPATH ];
+ vec3_t mins, maxs;
+ int i;
+
+ weaponInfo = &cg_weapons[ weaponNum ];
+
+ if( weaponNum == 0 )
+ return;
+
+ if( weaponInfo->registered )
+ return;
+
+ memset( weaponInfo, 0, sizeof( *weaponInfo ) );
+ weaponInfo->registered = qtrue;
+
+ if( !BG_FindNameForWeapon( weaponNum ) )
+ CG_Error( "Couldn't find weapon %i", weaponNum );
+
+ Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_FindNameForWeapon( weaponNum ) );
+
+ weaponInfo->humanName = BG_FindHumanNameForWeapon( weaponNum );
+
+ if( !CG_ParseWeaponFile( path, weaponInfo ) )
+ Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path );
+
+ // calc midpoint for rotation
+ trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs );
+ for( i = 0 ; i < 3 ; i++ )
+ weaponInfo->weaponMidpoint[ i ] = mins[ i ] + 0.5 * ( maxs[ i ] - mins[ i ] );
+
+ //FIXME:
+ for( i = WPM_NONE + 1; i < WPM_NUM_WEAPONMODES; i++ )
+ weaponInfo->wim[ i ].loopFireSound = qfalse;
+}
+
+/*
+===============
+CG_InitWeapons
+
+Precaches weapons
+===============
+*/
+void CG_InitWeapons( void )
+{
+ int i;
+
+ memset( cg_weapons, 0, sizeof( cg_weapons ) );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ CG_RegisterWeapon( i );
+
+ cgs.media.level2ZapTS = CG_RegisterTrailSystem( "models/weapons/lev2zap/lightning" );
+}
+
+
+/*
+========================================================================================
+
+VIEW WEAPON
+
+========================================================================================
+*/
+
+/*
+=================
+CG_MapTorsoToWeaponFrame
+
+=================
+*/
+static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame )
+{
+
+ // change weapon
+ if( frame >= ci->animations[ TORSO_DROP ].firstFrame &&
+ frame < ci->animations[ TORSO_DROP ].firstFrame + 9 )
+ return frame - ci->animations[ TORSO_DROP ].firstFrame + 6;
+
+ // stand attack
+ if( frame >= ci->animations[ TORSO_ATTACK ].firstFrame &&
+ frame < ci->animations[ TORSO_ATTACK ].firstFrame + 6 )
+ return 1 + frame - ci->animations[ TORSO_ATTACK ].firstFrame;
+
+ // stand attack 2
+ if( frame >= ci->animations[ TORSO_ATTACK2 ].firstFrame &&
+ frame < ci->animations[ TORSO_ATTACK2 ].firstFrame + 6 )
+ return 1 + frame - ci->animations[ TORSO_ATTACK2 ].firstFrame;
+
+ return 0;
+}
+
+
+/*
+==============
+CG_CalculateWeaponPosition
+==============
+*/
+static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles )
+{
+ float scale;
+ int delta;
+ float fracsin;
+ float bob;
+
+ VectorCopy( cg.refdef.vieworg, origin );
+ VectorCopy( cg.refdefViewAngles, angles );
+
+ // on odd legs, invert some angles
+ if( cg.bobcycle & 1 )
+ scale = -cg.xyspeed;
+ else
+ scale = cg.xyspeed;
+
+ // gun angles from bobbing
+ // bob amount is class dependant
+ bob = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] );
+
+ if( bob != 0 )
+ {
+ angles[ ROLL ] += scale * cg.bobfracsin * 0.005;
+ angles[ YAW ] += scale * cg.bobfracsin * 0.01;
+ angles[ PITCH ] += cg.xyspeed * cg.bobfracsin * 0.005;
+ }
+
+ // drop the weapon when landing
+ if( !BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_NOWEAPONDRIFT ) )
+ {
+ delta = cg.time - cg.landTime;
+ if( delta < LAND_DEFLECT_TIME )
+ origin[ 2 ] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME;
+ else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME )
+ origin[ 2 ] += cg.landChange*0.25 *
+ ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME;
+
+ // idle drift
+ scale = cg.xyspeed + 40;
+ fracsin = sin( cg.time * 0.001 );
+ angles[ ROLL ] += scale * fracsin * 0.01;
+ angles[ YAW ] += scale * fracsin * 0.01;
+ angles[ PITCH ] += scale * fracsin * 0.01;
+ }
+}
+
+
+/*
+======================
+CG_MachinegunSpinAngle
+======================
+*/
+#define SPIN_SPEED 0.9
+#define COAST_TIME 1000
+static float CG_MachinegunSpinAngle( centity_t *cent, qboolean firing )
+{
+ 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 == !firing )
+ {
+ cent->pe.barrelTime = cg.time;
+ cent->pe.barrelAngle = AngleMod( angle );
+ cent->pe.barrelSpinning = firing;
+ }
+
+ return angle;
+}
+
+
+/*
+=============
+CG_AddPlayerWeapon
+
+Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL)
+The main player will have this called for BOTH cases, so effects like light and
+sound should only be done on the world model case.
+=============
+*/
+void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent )
+{
+ refEntity_t gun;
+ refEntity_t barrel;
+ refEntity_t flash;
+ vec3_t angles;
+ weapon_t weaponNum;
+ weaponMode_t weaponMode;
+ weaponInfo_t *weapon;
+ qboolean noGunModel;
+ qboolean firing;
+
+ weaponNum = cent->currentState.weapon;
+ weaponMode = cent->currentState.generic1;
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ if( ( ( cent->currentState.eFlags & EF_FIRING ) && weaponMode == WPM_PRIMARY ) ||
+ ( ( cent->currentState.eFlags & EF_FIRING2 ) && weaponMode == WPM_SECONDARY ) ||
+ ( ( cent->currentState.eFlags & EF_FIRING3 ) && weaponMode == WPM_TERTIARY ) )
+ firing = qtrue;
+ else
+ firing = qfalse;
+
+ CG_RegisterWeapon( weaponNum );
+ weapon = &cg_weapons[ weaponNum ];
+
+ // add the weapon
+ memset( &gun, 0, sizeof( gun ) );
+ VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
+ gun.shadowPlane = parent->shadowPlane;
+ gun.renderfx = parent->renderfx;
+
+ // set custom shading for railgun refire rate
+ if( ps )
+ {
+ gun.shaderRGBA[ 0 ] = 255;
+ gun.shaderRGBA[ 1 ] = 255;
+ gun.shaderRGBA[ 2 ] = 255;
+ gun.shaderRGBA[ 3 ] = 255;
+
+ //set weapon[1/2]Time when respective buttons change state
+ if( cg.weapon1Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING ) )
+ {
+ cg.weapon1Time = cg.time;
+ cg.weapon1Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING );
+ }
+
+ if( cg.weapon2Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING2 ) )
+ {
+ cg.weapon2Time = cg.time;
+ cg.weapon2Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING2 );
+ }
+
+ if( cg.weapon3Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING3 ) )
+ {
+ cg.weapon3Time = cg.time;
+ cg.weapon3Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING3 );
+ }
+ }
+
+ gun.hModel = weapon->weaponModel;
+
+ noGunModel = ( ( !ps || cg.renderingThirdPerson ) && weapon->disableIn3rdPerson ) || !gun.hModel;
+
+ if( !ps )
+ {
+ // add weapon ready sound
+ if( firing && weapon->wim[ weaponMode ].firingSound )
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ weapon->wim[ weaponMode ].firingSound );
+ }
+ else if( weapon->readySound )
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound );
+ }
+
+ if( !noGunModel )
+ {
+ CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon" );
+
+ trap_R_AddRefEntityToScene( &gun );
+
+ // 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, firing );
+ AnglesToAxis( angles, barrel.axis );
+
+ CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" );
+
+ trap_R_AddRefEntityToScene( &barrel );
+ }
+ }
+
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ {
+ if( ps || cg.renderingThirdPerson ||
+ cent->currentState.number != cg.predictedPlayerState.clientNum )
+ {
+ if( noGunModel )
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" );
+ else
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" );
+ }
+
+ //if the PS is infinite disable it when not firing
+ if( !firing && CG_IsParticleSystemInfinite( cent->muzzlePS ) )
+ CG_DestroyParticleSystem( &cent->muzzlePS );
+ }
+
+ // add the flash
+ if( !weapon->wim[ weaponMode ].continuousFlash || !firing )
+ {
+ // impulse flash
+ if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME )
+ 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 )
+ {
+ angles[ YAW ] = 0;
+ angles[ PITCH ] = 0;
+ angles[ ROLL ] = crandom( ) * 10;
+ AnglesToAxis( angles, flash.axis );
+
+ if( noGunModel )
+ CG_PositionRotatedEntityOnTag( &flash, parent, parent->hModel, "tag_weapon" );
+ else
+ CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash" );
+
+ trap_R_AddRefEntityToScene( &flash );
+ }
+
+ if( ps || cg.renderingThirdPerson ||
+ cent->currentState.number != cg.predictedPlayerState.clientNum )
+ {
+ if( weapon->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger )
+ {
+ cent->muzzlePS = CG_SpawnNewParticleSystem( weapon->wim[ weaponMode ].muzzleParticleSystem );
+
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ {
+ if( noGunModel )
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" );
+ else
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" );
+
+ CG_SetAttachmentCent( &cent->muzzlePS->attachment, cent );
+ CG_AttachToTag( &cent->muzzlePS->attachment );
+ }
+
+ cent->muzzlePsTrigger = qfalse;
+ }
+
+ // make a dlight for the flash
+ if( weapon->wim[ weaponMode ].flashDlightColor[ 0 ] ||
+ weapon->wim[ weaponMode ].flashDlightColor[ 1 ] ||
+ weapon->wim[ weaponMode ].flashDlightColor[ 2 ] )
+ {
+ trap_R_AddLightToScene( flash.origin, 300 + ( rand( ) & 31 ),
+ weapon->wim[ weaponMode ].flashDlightColor[ 0 ],
+ weapon->wim[ weaponMode ].flashDlightColor[ 1 ],
+ weapon->wim[ weaponMode ].flashDlightColor[ 2 ] );
+ }
+ }
+}
+
+/*
+==============
+CG_AddViewWeapon
+
+Add the weapon, and flash for the player's view
+==============
+*/
+void CG_AddViewWeapon( playerState_t *ps )
+{
+ refEntity_t hand;
+ centity_t *cent;
+ clientInfo_t *ci;
+ float fovOffset;
+ vec3_t angles;
+ weaponInfo_t *wi;
+ weapon_t weapon = ps->weapon;
+ weaponMode_t weaponMode = ps->generic1;
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ CG_RegisterWeapon( weapon );
+ wi = &cg_weapons[ weapon ];
+ cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum];
+
+ if( ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) ||
+ ( ps->stats[ STAT_STATE ] & SS_INFESTING ) ||
+ ( ps->stats[ STAT_STATE ] & SS_HOVELING ) )
+ return;
+
+ // no weapon carried - can't draw it
+ if( weapon == WP_NONE )
+ return;
+
+ if( ps->pm_type == PM_INTERMISSION )
+ return;
+
+ // draw a prospective buildable infront of the player
+ if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
+ CG_GhostBuildable( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT );
+
+ if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 )
+ {
+ if( ps->stats[ STAT_MISC ] > ( LCANNON_TOTAL_CHARGE - ( LCANNON_TOTAL_CHARGE / 3 ) ) )
+ trap_S_AddLoopingSound( ps->clientNum, ps->origin, vec3_origin, cgs.media.lCannonWarningSound );
+ }
+
+ // no gun if in third person view
+ if( cg.renderingThirdPerson )
+ return;
+
+ // allow the gun to be completely removed
+ if( !cg_drawGun.integer )
+ {
+ vec3_t origin;
+
+ VectorCopy( cg.refdef.vieworg, origin );
+ VectorMA( origin, -8, cg.refdef.viewaxis[ 2 ], origin );
+
+ if( cent->muzzlePS )
+ CG_SetAttachmentPoint( &cent->muzzlePS->attachment, origin );
+
+ //check for particle systems
+ if( wi->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger )
+ {
+ cent->muzzlePS = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].muzzleParticleSystem );
+
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ {
+ CG_SetAttachmentPoint( &cent->muzzlePS->attachment, origin );
+ CG_SetAttachmentCent( &cent->muzzlePS->attachment, cent );
+ CG_AttachToPoint( &cent->muzzlePS->attachment );
+ }
+ cent->muzzlePsTrigger = qfalse;
+ }
+
+ return;
+ }
+
+ // don't draw if testing a gun model
+ if( cg.testGun )
+ return;
+
+ // drop gun lower at higher fov
+ if( cg.refdef.fov_y > 90 )
+ fovOffset = -0.4 * ( cg.refdef.fov_y - 90 );
+ else
+ fovOffset = 0;
+
+ memset( &hand, 0, sizeof( hand ) );
+
+ // set up gun position
+ CG_CalculateWeaponPosition( hand.origin, angles );
+
+ VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[ 0 ], hand.origin );
+ VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[ 1 ], hand.origin );
+ VectorMA( hand.origin, ( cg_gun_z.value + fovOffset ), cg.refdef.viewaxis[ 2 ], hand.origin );
+
+ if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 )
+ {
+ float fraction = (float)ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE;
+
+ VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ], hand.origin );
+ VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ], hand.origin );
+ }
+
+ AnglesToAxis( angles, hand.axis );
+
+ // map torso animations to weapon animations
+ if( cg_gun_frame.integer )
+ {
+ // development tool
+ hand.frame = hand.oldframe = cg_gun_frame.integer;
+ hand.backlerp = 0;
+ }
+ else
+ {
+ // get clientinfo for animation map
+ ci = &cgs.clientinfo[ cent->currentState.clientNum ];
+ hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame );
+ hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame );
+ hand.backlerp = cent->pe.torso.backlerp;
+ }
+
+ hand.hModel = wi->handsModel;
+ hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT;
+
+ // add everything onto the hand
+ CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity );
+}
+
+/*
+==============================================================================
+
+WEAPON SELECTION
+
+==============================================================================
+*/
+
+/*
+===============
+CG_WeaponSelectable
+===============
+*/
+static qboolean CG_WeaponSelectable( weapon_t weapon )
+{
+ //int ammo, clips;
+ //
+ //ammo = cg.snap->ps.ammo;
+ //clips = cg.snap->ps.clips
+ //
+ // this is a pain in the ass
+ //if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) )
+ // return qfalse;
+
+ if( !BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+/*
+===============
+CG_UpgradeSelectable
+===============
+*/
+static qboolean CG_UpgradeSelectable( upgrade_t upgrade )
+{
+ if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) )
+ return qfalse;
+
+ return BG_FindUsableForUpgrade( upgrade );
+}
+
+
+#define ICON_BORDER 4
+
+/*
+===================
+CG_DrawItemSelect
+===================
+*/
+void CG_DrawItemSelect( rectDef_t *rect, vec4_t color )
+{
+ int i;
+ int x = rect->x;
+ int y = rect->y;
+ int width = rect->w;
+ int height = rect->h;
+ int iconsize;
+ int items[ 64 ];
+ int numItems = 0, selectedItem = 0;
+ int length;
+ int selectWindow;
+ qboolean vertical;
+ centity_t *cent;
+ playerState_t *ps;
+
+ int colinfo[ 64 ];
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ // don't display if dead
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ // first make sure that whatever it selected is actually selectable
+ if( cg.weaponSelect <= 32 && !CG_WeaponSelectable( cg.weaponSelect ) )
+ CG_NextWeapon_f( );
+ else if( cg.weaponSelect > 32 && !CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
+ CG_NextWeapon_f( );
+ }
+
+ // showing weapon select clears pickup item display, but not the blend blob
+ cg.itemPickupTime = 0;
+
+ if( height > width )
+ {
+ vertical = qtrue;
+ iconsize = width;
+ length = height / width;
+ }
+ else
+ {
+ vertical = qfalse;
+ iconsize = height;
+ length = width / height;
+ }
+
+ selectWindow = length / 2;
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) )
+ continue;
+
+ {
+ int ammo, clips;
+
+ ammo = cg.snap->ps.ammo;
+ clips = cg.snap->ps.clips;
+
+ if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) )
+ colinfo[ numItems ] = 1;
+ else
+ colinfo[ numItems ] = 0;
+
+ }
+
+ if( i == cg.weaponSelect )
+ selectedItem = numItems;
+
+ CG_RegisterWeapon( i );
+ items[ numItems ] = i;
+ numItems++;
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) )
+ continue;
+ colinfo[ numItems ] = 0;
+ if( !BG_FindUsableForUpgrade ( i ) )
+ colinfo[ numItems ] = 2;
+
+
+ if( i == cg.weaponSelect - 32 )
+ selectedItem = numItems;
+
+ CG_RegisterUpgrade( i );
+ items[ numItems ] = i + 32;
+ numItems++;
+ }
+
+ for( i = 0; i < length; i++ )
+ {
+ int displacement = i - selectWindow;
+ int item = displacement + selectedItem;
+
+ if( ( item >= 0 ) && ( item < numItems ) )
+ {
+ switch( colinfo[ item ] )
+ {
+ case 0:
+ color = colorCyan;
+ break;
+ case 1:
+ color = colorRed;
+ break;
+ case 2:
+ color = colorMdGrey;
+ break;
+ }
+ color[3] = 0.5;
+
+ trap_R_SetColor( color );
+
+ if( items[ item ] <= 32 )
+ CG_DrawPic( x, y, iconsize, iconsize, cg_weapons[ items[ item ] ].weaponIcon );
+ else if( items[ item ] > 32 )
+ CG_DrawPic( x, y, iconsize, iconsize, cg_upgrades[ items[ item ] - 32 ].upgradeIcon );
+
+ trap_R_SetColor( NULL );
+ }
+
+ if( vertical )
+ y += iconsize;
+ else
+ x += iconsize;
+ }
+}
+
+
+/*
+===================
+CG_DrawItemSelectText
+===================
+*/
+void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle )
+{
+ int x, w;
+ char *name;
+ float *color;
+
+ color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME );
+ if( !color )
+ return;
+
+ trap_R_SetColor( color );
+
+ // draw the selected name
+ if( cg.weaponSelect <= 32 )
+ {
+ if( cg_weapons[ cg.weaponSelect ].registered &&
+ BG_InventoryContainsWeapon( cg.weaponSelect, cg.snap->ps.stats ) )
+ {
+ if( ( name = cg_weapons[ cg.weaponSelect ].humanName ) )
+ {
+ w = CG_Text_Width( name, scale, 0 );
+ x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
+ }
+ }
+ }
+ else if( cg.weaponSelect > 32 )
+ {
+ if( cg_upgrades[ cg.weaponSelect - 32 ].registered &&
+ BG_InventoryContainsUpgrade( cg.weaponSelect - 32, cg.snap->ps.stats ) )
+ {
+ if( ( name = cg_upgrades[ cg.weaponSelect - 32 ].humanName ) )
+ {
+ w = CG_Text_Width( name, scale, 0 );
+ x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
+ }
+ }
+ }
+
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+===============
+CG_NextWeapon_f
+===============
+*/
+void CG_NextWeapon_f( void )
+{
+ int i;
+ int original;
+
+ if( !cg.snap )
+ return;
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ trap_SendClientCommand( "followprev\n" );
+ return;
+ }
+
+ cg.weaponSelectTime = cg.time;
+ original = cg.weaponSelect;
+
+ for( i = 0; i < 64; i++ )
+ {
+ cg.weaponSelect++;
+ if( cg.weaponSelect == 64 )
+ cg.weaponSelect = 0;
+
+ if( cg.weaponSelect <= 32 )
+ {
+ if( CG_WeaponSelectable( cg.weaponSelect ) )
+ break;
+ }
+ else if( cg.weaponSelect > 32 )
+ {
+ if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
+ break;
+ }
+ }
+
+ if( i == 64 )
+ cg.weaponSelect = original;
+}
+
+/*
+===============
+CG_PrevWeapon_f
+===============
+*/
+void CG_PrevWeapon_f( void )
+{
+ int i;
+ int original;
+
+ if( !cg.snap )
+ return;
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ trap_SendClientCommand( "follownext\n" );
+ return;
+ }
+
+ cg.weaponSelectTime = cg.time;
+ original = cg.weaponSelect;
+
+ for( i = 0; i < 64; i++ )
+ {
+ cg.weaponSelect--;
+ if( cg.weaponSelect == -1 )
+ cg.weaponSelect = 63;
+
+ if( cg.weaponSelect <= 32 )
+ {
+ if( CG_WeaponSelectable( cg.weaponSelect ) )
+ break;
+ }
+ else if( cg.weaponSelect > 32 )
+ {
+ if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
+ break;
+ }
+ }
+
+ if( i == 64 )
+ cg.weaponSelect = original;
+}
+
+/*
+===============
+CG_Weapon_f
+===============
+*/
+void CG_Weapon_f( void )
+{
+ int num;
+
+ if( !cg.snap )
+ return;
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ return;
+
+ num = atoi( CG_Argv( 1 ) );
+
+ if( num < 1 || num > 31 )
+ return;
+
+ cg.weaponSelectTime = cg.time;
+
+ if( !BG_InventoryContainsWeapon( num, cg.snap->ps.stats ) )
+ return; // don't have the weapon
+
+ cg.weaponSelect = num;
+}
+
+
+/*
+===================================================================================================
+
+WEAPON EVENTS
+
+===================================================================================================
+*/
+
+/*
+================
+CG_FireWeapon
+
+Caused by an EV_FIRE_WEAPON event
+================
+*/
+void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode )
+{
+ entityState_t *es;
+ int c;
+ weaponInfo_t *wi;
+ weapon_t weaponNum;
+
+ es = &cent->currentState;
+
+ weaponNum = es->weapon;
+
+ if( weaponNum == WP_NONE )
+ return;
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ if( weaponNum >= WP_NUM_WEAPONS )
+ {
+ CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
+ return;
+ }
+
+ wi = &cg_weapons[ weaponNum ];
+
+ // mark the entity as muzzle flashing, so when it is added it will
+ // append the flash to the weapon model
+ cent->muzzleFlashTime = cg.time;
+
+ if( wi->wim[ weaponMode ].muzzleParticleSystem )
+ {
+ if( !CG_IsParticleSystemValid( &cent->muzzlePS ) ||
+ !CG_IsParticleSystemInfinite( cent->muzzlePS ) )
+ cent->muzzlePsTrigger = qtrue;
+ }
+
+ // play a sound
+ for( c = 0; c < 4; c++ )
+ {
+ if( !wi->wim[ weaponMode ].flashSound[ c ] )
+ break;
+ }
+
+ if( c > 0 )
+ {
+ c = rand( ) % c;
+ if( wi->wim[ weaponMode ].flashSound[ c ] )
+ trap_S_StartSound( NULL, es->number, CHAN_WEAPON, wi->wim[ weaponMode ].flashSound[ c ] );
+ }
+}
+
+
+/*
+=================
+CG_MissileHitWall
+
+Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
+=================
+*/
+void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientNum,
+ vec3_t origin, vec3_t dir, impactSound_t soundType )
+{
+ qhandle_t mark = 0;
+ qhandle_t ps = 0;
+ int c;
+ float radius = 1.0f;
+ weaponInfo_t *weapon = &cg_weapons[ weaponNum ];
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ mark = weapon->wim[ weaponMode ].impactMark;
+ radius = weapon->wim[ weaponMode ].impactMarkSize;
+ ps = weapon->wim[ weaponMode ].impactParticleSystem;
+
+ if( soundType == IMPACTSOUND_FLESH )
+ {
+ //flesh sound
+ for( c = 0; c < 4; c++ )
+ {
+ if( !weapon->wim[ weaponMode ].impactFleshSound[ c ] )
+ break;
+ }
+
+ if( c > 0 )
+ {
+ c = rand( ) % c;
+ if( weapon->wim[ weaponMode ].impactFleshSound[ c ] )
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactFleshSound[ c ] );
+ }
+ }
+ else
+ {
+ //normal sound
+ for( c = 0; c < 4; c++ )
+ {
+ if( !weapon->wim[ weaponMode ].impactSound[ c ] )
+ break;
+ }
+
+ if( c > 0 )
+ {
+ c = rand( ) % c;
+ if( weapon->wim[ weaponMode ].impactSound[ c ] )
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactSound[ c ] );
+ }
+ }
+
+ //create impact particle system
+ if( ps )
+ {
+ particleSystem_t *partSystem = CG_SpawnNewParticleSystem( ps );
+
+ if( CG_IsParticleSystemValid( &partSystem ) )
+ {
+ CG_SetAttachmentPoint( &partSystem->attachment, origin );
+ CG_SetParticleSystemNormal( partSystem, dir );
+ CG_AttachToPoint( &partSystem->attachment );
+ }
+ }
+
+ //
+ // impact mark
+ //
+ if( radius > 0.0f )
+ CG_ImpactMark( mark, origin, dir, random( ) * 360, 1, 1, 1, 1, qfalse, radius, qfalse );
+}
+
+
+/*
+=================
+CG_MissileHitPlayer
+=================
+*/
+void CG_MissileHitPlayer( weapon_t weaponNum, weaponMode_t weaponMode,
+ vec3_t origin, vec3_t dir, int entityNum )
+{
+ vec3_t normal;
+ weaponInfo_t *weapon = &cg_weapons[ weaponNum ];
+
+ VectorCopy( dir, normal );
+ VectorInverse( normal );
+
+ CG_Bleed( origin, normal, entityNum );
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ if( weapon->wim[ weaponMode ].alwaysImpact )
+ CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, IMPACTSOUND_FLESH );
+}
+
+
+/*
+============================================================================
+
+BULLETS
+
+============================================================================
+*/
+
+
+/*
+===============
+CG_Tracer
+===============
+*/
+void CG_Tracer( vec3_t source, vec3_t dest )
+{
+ vec3_t forward, right;
+ polyVert_t verts[ 4 ];
+ vec3_t line;
+ float len, begin, end;
+ vec3_t start, finish;
+ vec3_t midpoint;
+
+ // tracer
+ VectorSubtract( dest, source, forward );
+ len = VectorNormalize( forward );
+
+ // start at least a little ways from the muzzle
+ if( len < 100 )
+ return;
+
+ begin = 50 + random( ) * ( len - 60 );
+ end = begin + cg_tracerLength.value;
+ if( end > len )
+ end = len;
+
+ VectorMA( source, begin, forward, start );
+ VectorMA( source, end, forward, finish );
+
+ line[ 0 ] = DotProduct( forward, cg.refdef.viewaxis[ 1 ] );
+ line[ 1 ] = DotProduct( forward, cg.refdef.viewaxis[ 2 ] );
+
+ VectorScale( cg.refdef.viewaxis[ 1 ], line[ 1 ], right );
+ VectorMA( right, -line[ 0 ], cg.refdef.viewaxis[ 2 ], right );
+ VectorNormalize( right );
+
+ VectorMA( finish, cg_tracerWidth.value, right, verts[ 0 ].xyz );
+ verts[ 0 ].st[ 0 ] = 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;
+
+ VectorMA( finish, -cg_tracerWidth.value, right, verts[ 1 ].xyz );
+ verts[ 1 ].st[ 0 ] = 1;
+ verts[ 1 ].st[ 1 ] = 0;
+ verts[ 1 ].modulate[ 0 ] = 255;
+ verts[ 1 ].modulate[ 1 ] = 255;
+ verts[ 1 ].modulate[ 2 ] = 255;
+ verts[ 1 ].modulate[ 3 ] = 255;
+
+ VectorMA( start, -cg_tracerWidth.value, right, verts[ 2 ].xyz );
+ verts[ 2 ].st[ 0 ] = 1;
+ verts[ 2 ].st[ 1 ] = 1;
+ verts[ 2 ].modulate[ 0 ] = 255;
+ verts[ 2 ].modulate[ 1 ] = 255;
+ verts[ 2 ].modulate[ 2 ] = 255;
+ verts[ 2 ].modulate[ 3 ] = 255;
+
+ VectorMA( 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;
+
+ trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts );
+
+ midpoint[ 0 ] = ( start[ 0 ] + finish[ 0 ] ) * 0.5;
+ midpoint[ 1 ] = ( start[ 1 ] + finish[ 1 ] ) * 0.5;
+ midpoint[ 2 ] = ( start[ 2 ] + finish[ 2 ] ) * 0.5;
+
+ // add the tracer sound
+ trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound );
+}
+
+
+/*
+======================
+CG_CalcMuzzlePoint
+======================
+*/
+static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle )
+{
+ vec3_t forward;
+ centity_t *cent;
+ int anim;
+
+ if( entityNum == cg.snap->ps.clientNum )
+ {
+ VectorCopy( cg.snap->ps.origin, muzzle );
+ muzzle[ 2 ] += cg.snap->ps.viewheight;
+ AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL );
+ VectorMA( muzzle, 14, forward, muzzle );
+ return qtrue;
+ }
+
+ cent = &cg_entities[entityNum];
+
+ if( !cent->currentValid )
+ return qfalse;
+
+ VectorCopy( cent->currentState.pos.trBase, muzzle );
+
+ AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL );
+ anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
+
+ if( anim == LEGS_WALKCR || anim == LEGS_IDLECR )
+ muzzle[ 2 ] += CROUCH_VIEWHEIGHT;
+ else
+ muzzle[ 2 ] += DEFAULT_VIEWHEIGHT;
+
+ VectorMA( muzzle, 14, forward, muzzle );
+
+ return qtrue;
+
+}
+
+
+/*
+======================
+CG_Bullet
+
+Renders bullet effects.
+======================
+*/
+void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum )
+{
+ vec3_t start;
+
+ // if the shooter is currently valid, calc a source point and possibly
+ // do trail effects
+ if( sourceEntityNum >= 0 && cg_tracerChance.value > 0 )
+ {
+ if( CG_CalcMuzzlePoint( sourceEntityNum, start ) )
+ {
+ // draw a tracer
+ if( random( ) < cg_tracerChance.value )
+ CG_Tracer( start, end );
+ }
+ }
+
+ // impact splash and mark
+ if( flesh )
+ CG_Bleed( end, normal, fleshEntityNum );
+ else
+ CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT );
+}
+
+/*
+============================================================================
+
+SHOTGUN TRACING
+
+============================================================================
+*/
+
+/*
+================
+CG_ShotgunPattern
+
+Perform the same traces the server did to locate the
+hit splashes
+================
+*/
+static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum )
+{
+ int i;
+ float r, u;
+ vec3_t end;
+ vec3_t forward, right, up;
+ trace_t tr;
+
+ // derive the right and up vectors from the forward vector, because
+ // the client won't have any other information
+ VectorNormalize2( origin2, forward );
+ PerpendicularVector( right, forward );
+ CrossProduct( forward, right, up );
+
+ // generate the "random" spread pattern
+ for( i = 0; i < SHOTGUN_PELLETS; i++ )
+ {
+ r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
+ u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
+ VectorMA( origin, 8192 * 16, forward, end );
+ VectorMA( end, r, right, end );
+ VectorMA( end, u, up, end );
+
+ CG_Trace( &tr, origin, NULL, NULL, end, otherEntNum, MASK_SHOT );
+
+ if( !( tr.surfaceFlags & SURF_NOIMPACT ) )
+ {
+ if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER )
+ CG_MissileHitPlayer( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum );
+ else if( tr.surfaceFlags & SURF_METALSTEPS )
+ CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL );
+ else
+ CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT );
+ }
+ }
+}
+
+/*
+==============
+CG_ShotgunFire
+==============
+*/
+void CG_ShotgunFire( entityState_t *es )
+{
+ vec3_t v;
+
+ VectorSubtract( es->origin2, es->pos.trBase, v );
+ VectorNormalize( v );
+ VectorScale( v, 32, v );
+ VectorAdd( es->pos.trBase, v, v );
+
+ CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum );
+}
+
diff --git a/src/client/keycodes.h b/src/client/keycodes.h
new file mode 100644
index 0000000..ae6f189
--- /dev/null
+++ b/src/client/keycodes.h
@@ -0,0 +1,278 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __KEYCODES_H__
+#define __KEYCODES_H__
+
+//
+// these are the key numbers that should be passed to KeyEvent
+//
+
+// normal keys should be passed as lowercased ascii
+
+typedef enum {
+ K_NONE = -1,
+ K_TAB = 9,
+ K_ENTER = 13,
+ K_ESCAPE = 27,
+ K_SPACE = 32,
+
+ K_BACKSPACE = 127,
+
+ K_COMMAND = 128,
+ K_CAPSLOCK,
+ K_POWER,
+ K_PAUSE,
+
+ K_UPARROW,
+ K_DOWNARROW,
+ K_LEFTARROW,
+ K_RIGHTARROW,
+
+ K_ALT,
+ K_CTRL,
+ K_SHIFT,
+ K_INS,
+ K_DEL,
+ K_PGDN,
+ K_PGUP,
+ K_HOME,
+ K_END,
+
+ K_F1,
+ K_F2,
+ K_F3,
+ K_F4,
+ K_F5,
+ K_F6,
+ K_F7,
+ K_F8,
+ K_F9,
+ K_F10,
+ K_F11,
+ K_F12,
+ K_F13,
+ K_F14,
+ K_F15,
+
+ K_KP_HOME,
+ K_KP_UPARROW,
+ K_KP_PGUP,
+ K_KP_LEFTARROW,
+ K_KP_5,
+ K_KP_RIGHTARROW,
+ K_KP_END,
+ K_KP_DOWNARROW,
+ K_KP_PGDN,
+ K_KP_ENTER,
+ K_KP_INS,
+ K_KP_DEL,
+ K_KP_SLASH,
+ K_KP_MINUS,
+ K_KP_PLUS,
+ K_KP_NUMLOCK,
+ K_KP_STAR,
+ K_KP_EQUALS,
+
+ K_MOUSE1,
+ K_MOUSE2,
+ K_MOUSE3,
+ K_MOUSE4,
+ K_MOUSE5,
+
+ K_MWHEELDOWN,
+ K_MWHEELUP,
+
+ K_JOY1,
+ K_JOY2,
+ K_JOY3,
+ K_JOY4,
+ K_JOY5,
+ K_JOY6,
+ K_JOY7,
+ K_JOY8,
+ K_JOY9,
+ K_JOY10,
+ K_JOY11,
+ K_JOY12,
+ K_JOY13,
+ K_JOY14,
+ K_JOY15,
+ K_JOY16,
+ K_JOY17,
+ K_JOY18,
+ K_JOY19,
+ K_JOY20,
+ K_JOY21,
+ K_JOY22,
+ K_JOY23,
+ K_JOY24,
+ K_JOY25,
+ K_JOY26,
+ K_JOY27,
+ K_JOY28,
+ K_JOY29,
+ K_JOY30,
+ K_JOY31,
+ K_JOY32,
+
+ K_AUX1,
+ K_AUX2,
+ K_AUX3,
+ K_AUX4,
+ K_AUX5,
+ K_AUX6,
+ K_AUX7,
+ K_AUX8,
+ K_AUX9,
+ K_AUX10,
+ K_AUX11,
+ K_AUX12,
+ K_AUX13,
+ K_AUX14,
+ K_AUX15,
+ K_AUX16,
+
+ K_WORLD_0,
+ K_WORLD_1,
+ K_WORLD_2,
+ K_WORLD_3,
+ K_WORLD_4,
+ K_WORLD_5,
+ K_WORLD_6,
+ K_WORLD_7,
+ K_WORLD_8,
+ K_WORLD_9,
+ K_WORLD_10,
+ K_WORLD_11,
+ K_WORLD_12,
+ K_WORLD_13,
+ K_WORLD_14,
+ K_WORLD_15,
+ K_WORLD_16,
+ K_WORLD_17,
+ K_WORLD_18,
+ K_WORLD_19,
+ K_WORLD_20,
+ K_WORLD_21,
+ K_WORLD_22,
+ K_WORLD_23,
+ K_WORLD_24,
+ K_WORLD_25,
+ K_WORLD_26,
+ K_WORLD_27,
+ K_WORLD_28,
+ K_WORLD_29,
+ K_WORLD_30,
+ K_WORLD_31,
+ K_WORLD_32,
+ K_WORLD_33,
+ K_WORLD_34,
+ K_WORLD_35,
+ K_WORLD_36,
+ K_WORLD_37,
+ K_WORLD_38,
+ K_WORLD_39,
+ K_WORLD_40,
+ K_WORLD_41,
+ K_WORLD_42,
+ K_WORLD_43,
+ K_WORLD_44,
+ K_WORLD_45,
+ K_WORLD_46,
+ K_WORLD_47,
+ K_WORLD_48,
+ K_WORLD_49,
+ K_WORLD_50,
+ K_WORLD_51,
+ K_WORLD_52,
+ K_WORLD_53,
+ K_WORLD_54,
+ K_WORLD_55,
+ K_WORLD_56,
+ K_WORLD_57,
+ K_WORLD_58,
+ K_WORLD_59,
+ K_WORLD_60,
+ K_WORLD_61,
+ K_WORLD_62,
+ K_WORLD_63,
+ K_WORLD_64,
+ K_WORLD_65,
+ K_WORLD_66,
+ K_WORLD_67,
+ K_WORLD_68,
+ K_WORLD_69,
+ K_WORLD_70,
+ K_WORLD_71,
+ K_WORLD_72,
+ K_WORLD_73,
+ K_WORLD_74,
+ K_WORLD_75,
+ K_WORLD_76,
+ K_WORLD_77,
+ K_WORLD_78,
+ K_WORLD_79,
+ K_WORLD_80,
+ K_WORLD_81,
+ K_WORLD_82,
+ K_WORLD_83,
+ K_WORLD_84,
+ K_WORLD_85,
+ K_WORLD_86,
+ K_WORLD_87,
+ K_WORLD_88,
+ K_WORLD_89,
+ K_WORLD_90,
+ K_WORLD_91,
+ K_WORLD_92,
+ K_WORLD_93,
+ K_WORLD_94,
+ K_WORLD_95,
+
+ K_SUPER,
+ K_COMPOSE,
+ K_MODE,
+ K_HELP,
+ K_PRINT,
+ K_SYSREQ,
+ K_SCROLLOCK,
+ K_BREAK,
+ K_MENU,
+ K_EURO,
+ K_UNDO,
+
+ MAX_KEYS
+} keyNum_t;
+
+// MAX_KEYS replaces K_LAST_KEY, however some mods may have used K_LAST_KEY
+// in detecting binds, so we leave it defined to the old hardcoded value
+// of maxiumum keys to prevent mods from crashing older versions of the engine
+#define K_LAST_KEY 256
+
+// The menu code needs to get both key and char events, but
+// to avoid duplicating the paths, the char events are just
+// distinguished by or'ing in K_CHAR_FLAG (ugly)
+#define K_CHAR_FLAG 1024
+
+#endif
diff --git a/src/game/bg_lib.c b/src/game/bg_lib.c
new file mode 100644
index 0000000..69bca48
--- /dev/null
+++ b/src/game/bg_lib.c
@@ -0,0 +1,2045 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// bg_lib.c -- standard C library replacement routines used by code
+// compiled for the virtual machine
+
+
+#include "../qcommon/q_shared.h"
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "bg_lib.h"
+
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93";
+#endif
+static const char rcsid[] =
+ "$Id: bg_lib.c 965 2007-08-09 13:54:12Z msk $";
+#endif /* LIBC_SCCS and not lint */
+
+// bk001127 - needed for DLL's
+#if !defined( Q3_VM )
+typedef int cmp_t(const void *, const void *);
+#endif
+
+static char* med3(char *, char *, char *, cmp_t *);
+static void swapfunc(char *, char *, int, int);
+
+#ifndef min
+#define min(a, b) (a) < (b) ? a : b
+#endif
+
+/*
+ * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".
+ */
+#define swapcode(TYPE, parmi, parmj, n) { \
+ long i = (n) / sizeof (TYPE); \
+ register TYPE *pi = (TYPE *) (parmi); \
+ register TYPE *pj = (TYPE *) (parmj); \
+ do { \
+ register TYPE t = *pi; \
+ *pi++ = *pj; \
+ *pj++ = t; \
+ } while (--i > 0); \
+}
+
+#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
+ es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
+
+static void
+swapfunc(a, b, n, swaptype)
+ char *a, *b;
+ int n, swaptype;
+{
+ if(swaptype <= 1)
+ swapcode(long, a, b, n)
+ else
+ swapcode(char, a, b, n)
+}
+
+#define swap(a, b) \
+ if (swaptype == 0) { \
+ long t = *(long *)(a); \
+ *(long *)(a) = *(long *)(b); \
+ *(long *)(b) = t; \
+ } else \
+ swapfunc(a, b, es, swaptype)
+
+#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype)
+
+static char *
+med3(a, b, c, cmp)
+ char *a, *b, *c;
+ cmp_t *cmp;
+{
+ return cmp(a, b) < 0 ?
+ (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a ))
+ :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c ));
+}
+
+void
+qsort(a, n, es, cmp)
+ void *a;
+ size_t n, es;
+ cmp_t *cmp;
+{
+ char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
+ int d, r, swaptype, swap_cnt;
+
+loop: SWAPINIT(a, es);
+ swap_cnt = 0;
+ if (n < 7) {
+ for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
+ for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0;
+ pl -= es)
+ swap(pl, pl - es);
+ return;
+ }
+ pm = (char *)a + (n / 2) * es;
+ if (n > 7) {
+ pl = a;
+ pn = (char *)a + (n - 1) * es;
+ if (n > 40) {
+ d = (n / 8) * es;
+ pl = med3(pl, pl + d, pl + 2 * d, cmp);
+ pm = med3(pm - d, pm, pm + d, cmp);
+ pn = med3(pn - 2 * d, pn - d, pn, cmp);
+ }
+ pm = med3(pl, pm, pn, cmp);
+ }
+ swap(a, pm);
+ pa = pb = (char *)a + es;
+
+ pc = pd = (char *)a + (n - 1) * es;
+ for (;;) {
+ while (pb <= pc && (r = cmp(pb, a)) <= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ swap(pa, pb);
+ pa += es;
+ }
+ pb += es;
+ }
+ while (pb <= pc && (r = cmp(pc, a)) >= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ swap(pc, pd);
+ pd -= es;
+ }
+ pc -= es;
+ }
+ if (pb > pc)
+ break;
+ swap(pb, pc);
+ swap_cnt = 1;
+ pb += es;
+ pc -= es;
+ }
+ if (swap_cnt == 0) { /* Switch to insertion sort */
+ for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
+ for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0;
+ pl -= es)
+ swap(pl, pl - es);
+ return;
+ }
+
+ pn = (char *)a + n * es;
+ r = min(pa - (char *)a, pb - pa);
+ vecswap(a, pb - r, r);
+ r = min(pd - pc, pn - pd - es);
+ vecswap(pb, pn - r, r);
+ if ((r = pb - pa) > es)
+ qsort(a, r / es, es, cmp);
+ if ((r = pd - pc) > es) {
+ /* Iterate rather than recurse to save stack space */
+ a = pn - r;
+ n = r / es;
+ goto loop;
+ }
+/* qsort(pn - r, r / es, es, cmp);*/
+}
+
+//==================================================================================
+
+
+// this file is excluded from release builds because of intrinsics
+
+// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__'
+#if defined ( Q3_VM )
+
+size_t strlen( const char *string )
+{
+ const char *s;
+
+ s = string;
+ while( *s )
+ s++;
+
+ return s - string;
+}
+
+
+char *strcat( char *strDestination, const char *strSource )
+{
+ char *s;
+
+ s = strDestination;
+ while( *s )
+ s++;
+
+ while( *strSource )
+ *s++ = *strSource++;
+
+ *s = 0;
+ return strDestination;
+}
+
+char *strcpy( char *strDestination, const char *strSource )
+{
+ char *s;
+
+ s = strDestination;
+
+ while( *strSource )
+ *s++ = *strSource++;
+
+ *s = 0;
+ return strDestination;
+}
+
+
+int strcmp( const char *string1, const char *string2 )
+{
+ while( *string1 == *string2 && *string1 && *string2 )
+ {
+ string1++;
+ string2++;
+ }
+
+ return *string1 - *string2;
+}
+
+//TA:
+char *strrchr( const char *string, int c )
+{
+ int i, length = strlen( string );
+ char *p;
+
+ for( i = length - 1; i >= 0; i-- )
+ {
+ p = (char *)&string[ i ];
+
+ if( *p == c )
+ return (char *)p;
+ }
+
+ return (char *)0;
+}
+
+char *strchr( const char *string, int c )
+{
+ while( *string )
+ {
+ if( *string == c )
+ return ( char * )string;
+
+ string++;
+ }
+ return (char *)0;
+}
+
+char *strstr( const char *string, const char *strCharSet )
+{
+ while( *string )
+ {
+ int i;
+
+ for( i = 0; strCharSet[ i ]; i++ )
+ {
+ if( string[ i ] != strCharSet[ i ] )
+ break;
+ }
+
+ if( !strCharSet[ i ] )
+ return (char *)string;
+
+ string++;
+ }
+ return (char *)0;
+}
+
+#endif // bk001211
+
+#if defined ( Q3_VM )
+
+int tolower( int c )
+{
+ if( c >= 'A' && c <= 'Z' )
+ c += 'a' - 'A';
+
+ return c;
+}
+
+
+int toupper( int c )
+{
+ if( c >= 'a' && c <= 'z' )
+ c += 'A' - 'a';
+
+ return c;
+}
+
+#endif
+
+void *memmove( void *dest, const void *src, size_t count )
+{
+ int i;
+
+ if( dest > src )
+ {
+ for( i = count - 1; i >= 0; i-- )
+ ( (char *)dest )[ i ] = ( (char *)src )[ i ];
+ }
+ else
+ {
+ for( i = 0; i < count; i++ )
+ ( (char *)dest )[ i ] = ( (char *)src )[ i ];
+ }
+
+ return dest;
+}
+
+
+#if 0
+
+double floor( double x ) {
+ return (int)(x + 0x40000000) - 0x40000000;
+}
+
+void *memset( void *dest, int c, size_t count ) {
+ while ( count-- ) {
+ ((char *)dest)[count] = c;
+ }
+ return dest;
+}
+
+void *memcpy( void *dest, const void *src, size_t count ) {
+ while ( count-- ) {
+ ((char *)dest)[count] = ((char *)src)[count];
+ }
+ return dest;
+}
+
+char *strncpy( char *strDest, const char *strSource, size_t count ) {
+ char *s;
+
+ s = strDest;
+ while ( *strSource && count ) {
+ *s++ = *strSource++;
+ count--;
+ }
+ while ( count-- ) {
+ *s++ = 0;
+ }
+ return strDest;
+}
+
+double sqrt( double x ) {
+ float y;
+ float delta;
+ float maxError;
+
+ if ( x <= 0 ) {
+ return 0;
+ }
+
+ // initial guess
+ y = x / 2;
+
+ // refine
+ maxError = x * 0.001;
+
+ do {
+ delta = ( y * y ) - x;
+ y -= delta / ( 2 * y );
+ } while ( delta > maxError || delta < -maxError );
+
+ return y;
+}
+
+
+float sintable[1024] = {
+0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738,
+0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008,
+0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274,
+0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535,
+0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790,
+0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035,
+0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269,
+0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490,
+0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697,
+0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888,
+0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061,
+0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213,
+0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343,
+0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450,
+0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532,
+0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586,
+0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610,
+0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604,
+0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565,
+0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492,
+0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382,
+0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234,
+0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046,
+0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816,
+0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543,
+0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225,
+0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859,
+0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445,
+0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980,
+0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463,
+0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892,
+0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266,
+0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582,
+0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838,
+0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034,
+0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168,
+0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237,
+0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241,
+0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177,
+0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043,
+0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839,
+0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563,
+0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212,
+0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786,
+0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283,
+0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701,
+0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039,
+0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294,
+0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466,
+0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553,
+0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554,
+0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466,
+0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290,
+0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022,
+0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661,
+0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207,
+0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657,
+0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011,
+0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266,
+0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422,
+0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476,
+0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429,
+0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278,
+0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021,
+0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659,
+0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188,
+0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609,
+0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920,
+0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119,
+0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206,
+0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179,
+0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036,
+0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778,
+0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402,
+0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907,
+0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293,
+0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558,
+0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701,
+0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721,
+0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616,
+0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387,
+0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032,
+0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549,
+0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939,
+0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199,
+0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330,
+0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329,
+0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197,
+0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932,
+0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534,
+0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001,
+0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332,
+0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528,
+0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587,
+0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508,
+0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291,
+0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935,
+0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440,
+0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803,
+0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026,
+0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107,
+0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046,
+0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842,
+0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494,
+0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002,
+0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366,
+0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584,
+0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657,
+0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584,
+0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365,
+0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999,
+0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485,
+0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824,
+0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014,
+0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057,
+0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950,
+0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695,
+0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291,
+0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737,
+0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033,
+0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180,
+0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176,
+0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023,
+0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719,
+0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265,
+0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660,
+0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905,
+0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999
+};
+
+double sin( double x ) {
+ int index;
+ int quad;
+
+ index = 1024 * x / (M_PI * 0.5);
+ quad = ( index >> 10 ) & 3;
+ index &= 1023;
+ switch ( quad ) {
+ case 0:
+ return sintable[index];
+ case 1:
+ return sintable[1023-index];
+ case 2:
+ return -sintable[index];
+ case 3:
+ return -sintable[1023-index];
+ }
+ return 0;
+}
+
+
+double cos( double x ) {
+ int index;
+ int quad;
+
+ index = 1024 * x / (M_PI * 0.5);
+ quad = ( index >> 10 ) & 3;
+ index &= 1023;
+ switch ( quad ) {
+ case 3:
+ return sintable[index];
+ case 0:
+ return sintable[1023-index];
+ case 1:
+ return -sintable[index];
+ case 2:
+ return -sintable[1023-index];
+ }
+ return 0;
+}
+
+
+/*
+void create_acostable( void ) {
+ int i;
+ FILE *fp;
+ float a;
+
+ fp = fopen("c:\\acostable.txt", "w");
+ fprintf(fp, "float acostable[] = {");
+ for (i = 0; i < 1024; i++) {
+ if (!(i & 7))
+ fprintf(fp, "\n");
+ a = acos( (float) -1 + i / 512 );
+ fprintf(fp, "%1.8f,", a);
+ }
+ fprintf(fp, "\n}\n");
+ fclose(fp);
+}
+*/
+
+
+float acostable[] = {
+3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422,
+2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629,
+2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962,
+2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724,
+2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926,
+2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688,
+2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735,
+2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133,
+2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440,
+2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769,
+2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409,
+2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238,
+2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096,
+2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722,
+2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034,
+2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622,
+2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379,
+2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237,
+2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964,
+2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010,
+2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388,
+2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580,
+2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460,
+2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238,
+2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408,
+2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709,
+2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092,
+2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692,
+2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805,
+2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865,
+2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435,
+2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185,
+2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884,
+2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387,
+2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628,
+2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610,
+2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399,
+2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119,
+1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942,
+1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088,
+1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818,
+1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433,
+1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266,
+1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683,
+1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076,
+1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866,
+1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495,
+1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429,
+1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151,
+1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164,
+1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985,
+1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148,
+1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198,
+1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692,
+1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199,
+1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297,
+1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571,
+1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615,
+1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028,
+1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416,
+1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388,
+1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556,
+1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536,
+1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945,
+1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403,
+1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526,
+1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933,
+1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240,
+1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060,
+1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004,
+1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677,
+1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682,
+1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612,
+1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056,
+1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594,
+1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795,
+1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222,
+1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422,
+1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932,
+1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273,
+1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951,
+1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456,
+1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257,
+1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803,
+1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519,
+1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806,
+1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037,
+1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553,
+1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663,
+1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638,
+1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709,
+1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061,
+1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831,
+1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098,
+1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882,
+1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136,
+1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735,
+1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471,
+1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039,
+0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030,
+0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912,
+0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017,
+0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524,
+0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430,
+0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531,
+0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385,
+0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277,
+0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172,
+0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655,
+0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865,
+0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402,
+0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217,
+0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469,
+0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332,
+0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753,
+0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106,
+0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735,
+0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288,
+0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758,
+0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028,
+0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573,
+0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652,
+0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513,
+0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209,
+0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574,
+0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250,
+0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575,
+0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018,
+};
+
+double acos( double x ) {
+ int index;
+
+ if (x < -1)
+ x = -1;
+ if (x > 1)
+ x = 1;
+ index = (float) (1.0 + x) * 511.9;
+ return acostable[index];
+}
+
+
+double atan2( double y, double x ) {
+ float base;
+ float temp;
+ float dir;
+ float test;
+ int i;
+
+ if ( x < 0 ) {
+ if ( y >= 0 ) {
+ // quad 1
+ base = M_PI / 2;
+ temp = x;
+ x = y;
+ y = -temp;
+ } else {
+ // quad 2
+ base = M_PI;
+ x = -x;
+ y = -y;
+ }
+ } else {
+ if ( y < 0 ) {
+ // quad 3
+ base = 3 * M_PI / 2;
+ temp = x;
+ x = -y;
+ y = temp;
+ }
+ }
+
+ if ( y > x ) {
+ base += M_PI/2;
+ temp = x;
+ x = y;
+ y = temp;
+ dir = -1;
+ } else {
+ dir = 1;
+ }
+
+ // calcualte angle in octant 0
+ if ( x == 0 ) {
+ return base;
+ }
+ y /= x;
+
+ for ( i = 0 ; i < 512 ; i++ ) {
+ test = sintable[i] / sintable[1023-i];
+ if ( test > y ) {
+ break;
+ }
+ }
+
+ return base + dir * i * ( M_PI/2048);
+}
+
+
+#endif
+
+#ifdef Q3_VM
+/*
+===============
+rint
+===============
+*/
+double rint( double v )
+{
+ if( v >= 0.5f )
+ return ceil( v );
+ else
+ return floor( v );
+}
+
+// bk001127 - guarded this tan replacement
+// ld: undefined versioned symbol name tan@@GLIBC_2.0
+double tan( double x )
+{
+ return sin( x ) / cos( x );
+}
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice
+ * is preserved.
+ * ====================================================
+ */
+
+typedef union
+{
+ float value;
+ unsigned int word;
+} ieee_float_shape_type;
+
+/* Get a 32 bit int from a float. */
+
+#define GET_FLOAT_WORD(i,d) \
+do { \
+ ieee_float_shape_type gf_u; \
+ gf_u.value = (d); \
+ (i) = gf_u.word; \
+} while (0)
+
+/* Set a float from a 32 bit int. */
+
+#define SET_FLOAT_WORD(d,i) \
+do { \
+ ieee_float_shape_type sf_u; \
+ sf_u.word = (i); \
+ (d) = sf_u.value; \
+} while (0)
+
+/* A union which permits us to convert between a float and a 32 bit
+ int. */
+
+//acos
+static const float
+pi = 3.1415925026e+00, /* 0x40490fda */
+pio2_hi = 1.5707962513e+00, /* 0x3fc90fda */
+pio2_lo = 7.5497894159e-08, /* 0x33a22168 */
+pS0 = 1.6666667163e-01, /* 0x3e2aaaab */
+pS1 = -3.2556581497e-01, /* 0xbea6b090 */
+pS2 = 2.0121252537e-01, /* 0x3e4e0aa8 */
+pS3 = -4.0055535734e-02, /* 0xbd241146 */
+pS4 = 7.9153501429e-04, /* 0x3a4f7f04 */
+pS5 = 3.4793309169e-05, /* 0x3811ef08 */
+qS1 = -2.4033949375e+00, /* 0xc019d139 */
+qS2 = 2.0209457874e+00, /* 0x4001572d */
+qS3 = -6.8828397989e-01, /* 0xbf303361 */
+qS4 = 7.7038154006e-02; /* 0x3d9dc62e */
+
+/*
+==================
+acos
+==================
+*/
+double acos( double x )
+{
+ float z, subp, p, q, r, w, s, c, df;
+ int hx, ix;
+
+ GET_FLOAT_WORD( hx, x );
+ ix = hx & 0x7fffffff;
+
+ if( ix == 0x3f800000 )
+ { // |x|==1
+ if( hx > 0 )
+ return 0.0; // acos(1) = 0
+ else
+ return pi + (float)2.0 * pio2_lo; // acos(-1)= pi
+ }
+ else if( ix > 0x3f800000 )
+ { // |x| >= 1
+ return (x-x)/(x-x); // acos(|x|>1) is NaN
+ }
+
+ if( ix < 0x3f000000 )
+ { // |x| < 0.5
+ if( ix <= 0x23000000 )
+ return pio2_hi + pio2_lo;//if|x|<2**-57
+
+ z = x * x;
+ subp = pS3 + z * ( pS4 + z * pS5 );
+ // chop up expression to keep mac register based stack happy
+ p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) );
+ q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) );
+ r = p / q;
+ return pio2_hi - ( x - ( pio2_lo - x * r ) );
+ }
+ else if( hx < 0 )
+ { // x < -0.5
+ z = ( 1.0 + x ) * (float)0.5;
+ subp = pS3 + z * ( pS4 + z * pS5 );
+ // chop up expression to keep mac register based stack happy
+ p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) );
+ q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) );
+ s = sqrt( z );
+ r = p / q;
+ w = r * s - pio2_lo;
+ return pi - (float)2.0 * ( s + w );
+ }
+ else
+ { // x > 0.5
+ int idf;
+ z = ( 1.0 - x ) * (float)0.5;
+ s = sqrt( z );
+ df = s;
+ GET_FLOAT_WORD( idf, df );
+ SET_FLOAT_WORD( df, idf & 0xfffff000 );
+ c = ( z - df * df ) / ( s + df );
+ subp = pS3 + z * ( pS4 + z * pS5 );
+ // chop up expression to keep mac register based stack happy
+ p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) );
+ q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) );
+ r = p / q;
+ w = r * s + c;
+ return (double)( 2.0 * ( df + w ) );
+ }
+}
+
+//pow
+static const float
+bp[ ] = { 1.0, 1.5, },
+dp_h[ ] = { 0.0, 5.84960938e-01, }, /* 0x3f15c000 */
+dp_l[ ] = { 0.0, 1.56322085e-06, }, /* 0x35d1cfdc */
+huge = 1.0e+30,
+tiny = 1.0e-30,
+zero = 0.0,
+one = 1.0,
+two = 2.0,
+two24 = 16777216.0, /* 0x4b800000 */
+two25 = 3.355443200e+07, /* 0x4c000000 */
+twom25 = 2.9802322388e-08, /* 0x33000000 */
+ /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */
+L1 = 6.0000002384e-01, /* 0x3f19999a */
+L2 = 4.2857143283e-01, /* 0x3edb6db7 */
+L3 = 3.3333334327e-01, /* 0x3eaaaaab */
+L4 = 2.7272811532e-01, /* 0x3e8ba305 */
+L5 = 2.3066075146e-01, /* 0x3e6c3255 */
+L6 = 2.0697501302e-01, /* 0x3e53f142 */
+P1 = 1.6666667163e-01, /* 0x3e2aaaab */
+P2 = -2.7777778450e-03, /* 0xbb360b61 */
+P3 = 6.6137559770e-05, /* 0x388ab355 */
+P4 = -1.6533901999e-06, /* 0xb5ddea0e */
+P5 = 4.1381369442e-08, /* 0x3331bb4c */
+lg2 = 6.9314718246e-01, /* 0x3f317218 */
+lg2_h = 6.93145752e-01, /* 0x3f317200 */
+lg2_l = 1.42860654e-06, /* 0x35bfbe8c */
+ovt = 4.2995665694e-08, /* -(128-log2(ovfl+.5ulp)) */
+cp = 9.6179670095e-01, /* 0x3f76384f =2/(3ln2) */
+cp_h = 9.6179199219e-01, /* 0x3f763800 =head of cp */
+cp_l = 4.7017383622e-06, /* 0x369dc3a0 =tail of cp_h */
+ivln2 = 1.4426950216e+00, /* 0x3fb8aa3b =1/ln2 */
+ivln2_h = 1.4426879883e+00, /* 0x3fb8aa00 =16b 1/ln2*/
+ivln2_l = 7.0526075433e-06; /* 0x36eca570 =1/ln2 tail*/
+
+/*
+==================
+copysignf
+==================
+*/
+static float copysignf( float x, float y )
+{
+ unsigned int ix, iy;
+
+ GET_FLOAT_WORD( ix, x );
+ GET_FLOAT_WORD( iy, y );
+ SET_FLOAT_WORD( x, ( ix & 0x7fffffff ) | ( iy & 0x80000000 ) );
+ return x;
+}
+
+/*
+==================
+__scalbnf
+==================
+*/
+static float __scalbnf( float x, int n )
+{
+ int k, ix;
+
+ GET_FLOAT_WORD( ix, x );
+
+ k = ( ix & 0x7f800000 ) >> 23; /* extract exponent */
+
+ if( k == 0 )
+ { /* 0 or subnormal x */
+ if( ( ix & 0x7fffffff ) == 0 )
+ return x; /* +-0 */
+
+ x *= two25;
+ GET_FLOAT_WORD( ix, x );
+ k = ( ( ix & 0x7f800000 ) >> 23 ) - 25;
+ }
+ if( k == 0xff )
+ return x+x; /* NaN or Inf */
+
+ k = k + n;
+
+ if( n > 50000 || k > 0xfe )
+ return huge * copysignf( huge, x ); /* overflow */
+ if ( n < -50000 )
+ return tiny * 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*/
+
+ k += 25; /* subnormal result */
+ SET_FLOAT_WORD( x, ( ix & 0x807fffff ) | ( k << 23 ) );
+ return x * twom25;
+}
+
+/*
+==================
+pow
+==================
+*/
+float pow( float x, float y )
+{
+ float z, ax, z_h, z_l, p_h, p_l;
+ float y1, subt1, t1, t2, subr, r, s, t, u, v, w;
+ int i, j, k, yisint, n;
+ int hx, hy, ix, iy, is;
+
+ /*TA: for some reason the Q3 VM goes apeshit when x = 1.0
+ and y > 1.0. Curiously this doesn't happen with gcc
+ hence this hack*/
+ if( x == 1.0 )
+ return x;
+
+ GET_FLOAT_WORD( hx, x );
+ GET_FLOAT_WORD( hy, y );
+ ix = hx & 0x7fffffff;
+ iy = hy & 0x7fffffff;
+
+ /* y==zero: x**0 = 1 */
+ if( iy == 0 )
+ return one;
+
+ /* +-NaN return x+y */
+ if( ix > 0x7f800000 || iy > 0x7f800000 )
+ return x + y;
+
+ /* determine if y is an odd int when x < 0
+ * yisint = 0 ... y is not an integer
+ * yisint = 1 ... y is an odd int
+ * yisint = 2 ... y is an even int
+ */
+ yisint = 0;
+ if( hx < 0 )
+ {
+ if( iy >= 0x4b800000 )
+ yisint = 2; /* even integer y */
+ else if( iy >= 0x3f800000 )
+ {
+ k = ( iy >> 23 ) - 0x7f; /* exponent */
+ j = iy >> ( 23 - k );
+ if( ( j << ( 23 - k ) ) == iy )
+ yisint = 2 - ( j & 1 );
+ }
+ }
+
+ /* special value of y */
+ if( iy == 0x7f800000 )
+ { /* y is +-inf */
+ if( ix == 0x3f800000 )
+ return y - y; /* inf**+-1 is NaN */
+ else if( ix > 0x3f800000 )/* (|x|>1)**+-inf = inf,0 */
+ return ( hy >= 0 ) ? y : zero;
+ else /* (|x|<1)**-,+inf = inf,0 */
+ return ( hy < 0 ) ? -y : zero;
+ }
+
+ if( iy == 0x3f800000 )
+ { /* y is +-1 */
+ if( hy < 0 )
+ return one / x;
+ else
+ return x;
+ }
+
+ if( hy == 0x40000000 )
+ return x * x; /* y is 2 */
+
+ if( hy == 0x3f000000 )
+ { /* y is 0.5 */
+ if( hx >= 0 ) /* x >= +0 */
+ return sqrt( x );
+ }
+
+ ax = fabs( x );
+
+ /* special value of x */
+ if( ix == 0x7f800000 || ix == 0 || ix == 0x3f800000 )
+ {
+ z = ax; /*x is +-0,+-inf,+-1*/
+ if( hy < 0 )
+ z = one / z; /* z = (1/|x|) */
+ if( hx < 0 )
+ {
+ if( ( ( ix - 0x3f800000 ) | yisint ) == 0 )
+ z = ( z - z ) / ( z - z ); /* (-1)**non-int is NaN */
+ else if( yisint == 1 )
+ z = -z; /* (x<0)**odd = -(|x|**odd) */
+ }
+
+ return z;
+ }
+
+ /* (x<0)**(non-int) is NaN */
+ if( ( ( ( (unsigned int)hx >> 31 ) - 1 ) | yisint ) == 0 )
+ return ( x - x ) / ( x - x );
+
+ /* |y| is huge */
+ if( iy > 0x4d000000 )
+ { /* if |y| > 2**27 */
+ /* over/underflow if x is not close to one */
+ if( ix < 0x3f7ffff8 )
+ return ( hy < 0 ) ? huge * huge : tiny * tiny;
+
+ if( ix > 0x3f800007 )
+ return ( hy > 0 ) ? huge * huge : tiny * tiny;
+ /* now |1-x| is tiny <= 2**-20, suffice to compute
+ log(x) by x-x^2/2+x^3/3-x^4/4 */
+ t = x - 1; /* t has 20 trailing zeros */
+ w = ( t * t ) * ( (float)0.5 - t * ( (float)0.333333333333 - t * (float)0.25 ) );
+ u = ivln2_h * t; /* ivln2_h has 16 sig. bits */
+ v = t * ivln2_l - w * ivln2;
+ t1 = u + v;
+ GET_FLOAT_WORD( is, t1 );
+ SET_FLOAT_WORD( t1, is & 0xfffff000 );
+ t2 = v - ( t1 - u );
+ }
+ else
+ {
+ float s2, s_h, s_l, t_h, t_l;
+ n = 0;
+ /* take care subnormal number */
+ if( ix < 0x00800000 )
+ {
+ ax *= two24;
+ n -= 24;
+ GET_FLOAT_WORD( ix, ax );
+ }
+
+ n += ( ( ix ) >> 23 ) - 0x7f;
+ j = ix & 0x007fffff;
+
+ /* determine interval */
+ ix = j | 0x3f800000; /* normalize ix */
+ if( j <= 0x1cc471 )
+ k = 0; /* |x|<sqrt(3/2) */
+ else if( j < 0x5db3d7 )
+ k = 1; /* |x|<sqrt(3) */
+ else
+ {
+ k = 0;
+ n += 1;
+ ix -= 0x00800000;
+ }
+ SET_FLOAT_WORD( ax, ix );
+
+ /* compute s = s_h+s_l = (x-1)/(x+1) or (x-1.5)/(x+1.5) */
+ u = ax - bp[ k ]; /* bp[0]=1.0, bp[1]=1.5 */
+ v = one / ( ax + bp[ k ] );
+ s = u * v;
+ s_h = s;
+ GET_FLOAT_WORD( is, s_h );
+ SET_FLOAT_WORD( s_h, is & 0xfffff000 );
+ /* t_h=ax+bp[k] High */
+ SET_FLOAT_WORD( t_h, ( ( ix >> 1 ) | 0x20000000 ) + 0x0040000 + ( k << 21 ) );
+ t_l = ax - ( t_h - bp[ k ] );
+ s_l = v * ( ( u - s_h * t_h ) - s_h * t_l );
+ /* compute log(ax) */
+ s2 = s * s;
+ subr = L3 + s2 * ( L4 + s2 * ( L5 + s2 * L6 ) );
+ // chop up expression to keep mac register based stack happy
+ r = s2 * s2 * ( L1 + s2 * ( L2 + s2 * subr ) );
+ r += s_l * ( s_h + s );
+ s2 = s_h * s_h;
+ t_h = (float)3.0 + s2 + r;
+ GET_FLOAT_WORD( is, t_h );
+ SET_FLOAT_WORD( t_h, is & 0xfffff000 );
+ t_l = r - ( ( t_h - (float)3.0 ) - s2 );
+ /* u+v = s*(1+...) */
+ u = s_h * t_h;
+ v = s_l * t_h + t_l * s;
+ /* 2/(3log2)*(s+...) */
+ p_h = u + v;
+ GET_FLOAT_WORD( is, p_h );
+ SET_FLOAT_WORD( p_h, is & 0xfffff000 );
+ p_l = v - ( p_h - u );
+ z_h = cp_h * p_h; /* cp_h+cp_l = 2/(3*log2) */
+ z_l = cp_l * p_h + p_l * cp + dp_l[ k ];
+ /* log2(ax) = (s+..)*2/(3*log2) = n + dp_h + z_h + z_l */
+ t = (float)n;
+ t1 = ( ( ( z_h + z_l ) + dp_h[ k ] ) + t );
+ GET_FLOAT_WORD( is, t1 );
+ SET_FLOAT_WORD( t1, is & 0xfffff000 );
+ t2 = z_l - ( ( ( t1 - t ) - dp_h[ k ] ) - z_h );
+ }
+
+ s = one; /* s (sign of result -ve**odd) = -1 else = 1 */
+ if( ( ( ( (unsigned int)hx >> 31 ) - 1 ) | ( yisint - 1 ) ) == 0 )
+ s = -one; /* (-ve)**(odd int) */
+
+ /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */
+ GET_FLOAT_WORD( is, y );
+ SET_FLOAT_WORD( y1, is & 0xfffff000 );
+ p_l = ( y - y1 ) * t1 + y * t2;
+ p_h = y1 * t1;
+ z = p_l + p_h;
+ GET_FLOAT_WORD( j, z );
+
+ if( j > 0x43000000 ) /* if z > 128 */
+ return s * huge * huge; /* overflow */
+ else if( j == 0x43000000 )
+ { /* if z == 128 */
+ if( p_l + ovt > z - p_h )
+ return s * huge * huge; /* overflow */
+ }
+ else if( ( j & 0x7fffffff ) > 0x43160000 ) /* z <= -150 */
+ return s * tiny * tiny; /* underflow */
+ else if( (unsigned int)j == 0xc3160000 )
+ { /* z == -150 */
+ if( p_l <= z - p_h )
+ return s * tiny * tiny; /* underflow */
+ }
+
+ /*
+ * compute 2**(p_h+p_l)
+ */
+ i = j & 0x7fffffff;
+ k = ( i >> 23 ) - 0x7f;
+ n = 0;
+
+ if( i > 0x3f000000 )
+ { /* if |z| > 0.5, set n = [z+0.5] */
+ n = j + ( 0x00800000 >> ( k + 1 ) );
+ k = ( ( n & 0x7fffffff ) >> 23 ) - 0x7f; /* new k for n */
+ SET_FLOAT_WORD( t, n & ~( 0x007fffff >> k ) );
+ n = ( ( n & 0x007fffff ) | 0x00800000 ) >> ( 23 - k );
+
+ if( j < 0 )
+ n = -n;
+
+ p_h -= t;
+ }
+
+ t = p_l + p_h;
+ GET_FLOAT_WORD( is, t );
+ SET_FLOAT_WORD( t, is & 0xfffff000 );
+ u = t * lg2_h;
+ v = ( p_l - ( t - p_h ) ) * lg2 + t * lg2_l;
+ z = u + v;
+ w = v - ( z - u );
+ t = z * z;
+ subt1 = P3 + t * ( P4 + t * P5 );
+ // chop up expression to keep mac register based stack happy
+ t1 = z - t * ( P1 + t * ( P2 + t * subt1 ) );
+ r = ( z * t1 ) / ( t1 - two ) - ( w + z * w );
+ z = one - ( r - z );
+ GET_FLOAT_WORD( j, z );
+ j += (n << 23 );
+
+ if( ( j >> 23 ) <= 0 )
+ z = __scalbnf( z, n ); /* subnormal output */
+ else
+ SET_FLOAT_WORD( z, j );
+
+ return s * z;
+}
+
+#endif
+
+
+
+static int randSeed = 0;
+
+void srand( unsigned seed )
+{
+ randSeed = seed;
+}
+
+int rand( void )
+{
+ randSeed = ( 69069 * randSeed + 1 );
+ return randSeed & 0x7fff;
+}
+
+double atof( const char *string )
+{
+ float sign;
+ float value;
+ int c;
+
+ // skip whitespace
+ while( *string <= ' ' )
+ {
+ if( !*string )
+ return 0;
+
+ string++;
+ }
+
+ // check sign
+ switch( *string )
+ {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+
+ case '-':
+ string++;
+ sign = -1;
+ break;
+
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ c = string[ 0 ];
+
+ if( c != '.' )
+ {
+ do
+ {
+ c = *string++;
+ if( c < '0' || c > '9' )
+ break;
+
+ c -= '0';
+ value = value * 10 + c;
+ } while( 1 );
+ }
+ else
+ string++;
+
+ // check for decimal point
+ if( c == '.' )
+ {
+ double fraction;
+
+ fraction = 0.1;
+ do
+ {
+ c = *string++;
+ if( c < '0' || c > '9' )
+ break;
+
+ c -= '0';
+ value += c * fraction;
+ fraction *= 0.1;
+ } while( 1 );
+
+ }
+
+ // not handling 10e10 notation...
+
+ return value * sign;
+}
+
+double _atof( const char **stringPtr )
+{
+ const char *string;
+ float sign;
+ float value;
+ int c = '0'; // bk001211 - uninitialized use possible
+
+ string = *stringPtr;
+
+ // skip whitespace
+ while( *string <= ' ' )
+ {
+ if( !*string )
+ {
+ *stringPtr = string;
+ return 0;
+ }
+
+ string++;
+ }
+
+ // check sign
+ switch( *string )
+ {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+
+ case '-':
+ string++;
+ sign = -1;
+ break;
+
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ if( string[ 0 ] != '.' )
+ {
+ do
+ {
+ c = *string++;
+ if( c < '0' || c > '9' )
+ break;
+
+ c -= '0';
+ value = value * 10 + c;
+ } while( 1 );
+ }
+
+ // check for decimal point
+ if( c == '.' )
+ {
+ double fraction;
+
+ fraction = 0.1;
+ do
+ {
+ c = *string++;
+ if( c < '0' || c > '9' )
+ break;
+
+ c -= '0';
+ value += c * fraction;
+ fraction *= 0.1;
+ } while( 1 );
+
+ }
+
+ // not handling 10e10 notation...
+ *stringPtr = string;
+
+ return value * sign;
+}
+
+
+#if defined ( Q3_VM )
+
+int atoi( const char *string )
+{
+ int sign;
+ int value;
+ int c;
+
+ // skip whitespace
+ while( *string <= ' ' )
+ {
+ if( !*string )
+ return 0;
+
+ string++;
+ }
+
+ // check sign
+ switch( *string )
+ {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+
+ case '-':
+ string++;
+ sign = -1;
+ break;
+
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ do
+ {
+ c = *string++;
+ if( c < '0' || c > '9' )
+ break;
+
+ c -= '0';
+ value = value * 10 + c;
+ } while( 1 );
+
+ // not handling 10e10 notation...
+
+ return value * sign;
+}
+
+
+int _atoi( const char **stringPtr )
+{
+ int sign;
+ int value;
+ int c;
+ const char *string;
+
+ string = *stringPtr;
+
+ // skip whitespace
+ while( *string <= ' ' )
+ {
+ if( !*string )
+ return 0;
+
+ string++;
+ }
+
+ // check sign
+ switch( *string )
+ {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+
+ case '-':
+ string++;
+ sign = -1;
+ break;
+
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ do
+ {
+ c = *string++;
+ if( c < '0' || c > '9' )
+ break;
+
+ c -= '0';
+ value = value * 10 + c;
+ } while( 1 );
+
+ // not handling 10e10 notation...
+
+ *stringPtr = string;
+
+ return value * sign;
+}
+
+int abs( int n )
+{
+ return n < 0 ? -n : n;
+}
+
+double fabs( double x )
+{
+ return x < 0 ? -x : x;
+}
+
+unsigned int _hextoi( const char **stringPtr )
+{
+ unsigned int value;
+ int c;
+ int i;
+ const char *string;
+
+ string = *stringPtr;
+
+ // skip whitespace
+ while( *string <= ' ' )
+ {
+ if( !*string )
+ return 0;
+
+ string++;
+ }
+
+ value = 0;
+ i = 0;
+ while( i++ < 8 && ( c = *string++ ) )
+ {
+ if ( c >= '0' && c <= '9' )
+ {
+ value = value * 16 + c - '0';
+ continue;
+ }
+ else if ( c >= 'a' && c <= 'f' )
+ {
+ value = value * 16 + 10 + c - 'a';
+ continue;
+ }
+ else if ( c >= 'A' && c <= 'F' )
+ {
+ value = value * 16 + 10 + c - 'A';
+ continue;
+ }
+ else
+ break;
+ }
+ *stringPtr = string;
+ return value;
+}
+
+//=========================================================
+
+
+#define ALT 0x00000001 /* alternate form */
+#define HEX 0x00000002 /* hexadecimal */
+#define LADJUST 0x00000004 /* left adjustment */
+#define LONGDBL 0x00000008 /* long double */
+#define LONGINT 0x00000010 /* long integer */
+#define QUADINT 0x00000020 /* quad integer */
+#define SHORTINT 0x00000040 /* short integer */
+#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */
+#define FPT 0x00000100 /* floating point number */
+#define UNSIGNED 0x00000200 /* unsigned integer */
+
+#define to_digit(c) ((c) - '0')
+#define is_digit(c) ((unsigned)to_digit(c) <= 9)
+#define to_char(n) ((n) + '0')
+
+void AddInt( char **buf_p, int val, int width, int flags )
+{
+ char text[ 32 ];
+ int digits;
+ char *buf;
+
+ digits = 0;
+
+ if( flags & UNSIGNED )
+ val = (unsigned) val;
+
+ if( flags & HEX )
+ {
+ char c;
+ int n = 0;
+
+ while( n < 32 )
+ {
+ c = "0123456789abcdef"[ ( val >> n ) & 0xF ];
+ n += 4;
+ if( c == '0' && !digits )
+ continue;
+ text[ digits++ ] = c;
+ }
+ text[ digits ] = '\0';
+ }
+ else
+ {
+ int signedVal = val;
+
+ if( val < 0 )
+ val = -val;
+ do
+ {
+ text[ digits++ ] = '0' + val % 10;
+ val /= 10;
+ } while( val );
+
+ if( signedVal < 0 )
+ text[ digits++ ] = '-';
+ }
+
+ buf = *buf_p;
+
+ if( !( flags & LADJUST ) )
+ {
+ while( digits < width )
+ {
+ *buf++ = ( flags & ZEROPAD ) ? '0' : ' ';
+ width--;
+ }
+ }
+
+ while( digits-- )
+ {
+ *buf++ = text[ digits ];
+ width--;
+ }
+
+ if( flags & LADJUST )
+ {
+ while( width-- > 0 )
+ *buf++ = ( flags & ZEROPAD ) ? '0' : ' ';
+ }
+
+ *buf_p = buf;
+}
+
+void AddFloat( char **buf_p, float fval, int width, int prec )
+{
+ char text[ 32 ];
+ int digits;
+ float signedVal;
+ char *buf;
+ int val;
+
+ // get the sign
+ signedVal = fval;
+ if( fval < 0 )
+ fval = -fval;
+
+ // write the float number
+ digits = 0;
+ val = (int)fval;
+
+ do
+ {
+ text[ digits++ ] = '0' + val % 10;
+ val /= 10;
+ } while( val );
+
+ if( signedVal < 0 )
+ text[digits++] = '-';
+
+ buf = *buf_p;
+
+ while( digits < width )
+ {
+ *buf++ = ' ';
+ width--;
+ }
+
+ while( digits-- )
+ *buf++ = text[ digits ];
+
+ *buf_p = buf;
+
+ if( prec < 0 )
+ prec = 6;
+
+ // write the fraction
+ digits = 0;
+
+ while( digits < prec )
+ {
+ fval -= (int)fval;
+ fval *= 10.0;
+ val = (int)fval;
+ text[ digits++ ] = '0' + val % 10;
+ }
+
+ if( digits > 0 )
+ {
+ buf = *buf_p;
+ *buf++ = '.';
+ for( prec = 0; prec < digits; prec++ )
+ *buf++ = text[ prec ];
+
+ *buf_p = buf;
+ }
+}
+
+void AddVec3_t( char **buf_p, vec3_t v, int width, int prec )
+{
+ char *buf;
+
+ buf = *buf_p;
+
+ *buf++ = '[';
+
+ AddFloat( &buf, v[ 0 ], width, prec );
+ buf += width;
+ *buf++ = ' ';
+
+ AddFloat( &buf, v[ 1 ], width, prec );
+ buf += width;
+ *buf++ = ' ';
+
+ AddFloat( &buf, v[ 2 ], width, prec );
+ buf += width;
+ *buf++ = ']';
+
+ *buf_p = buf;
+}
+
+void AddString( char **buf_p, char *string, int width, int prec )
+{
+ int size;
+ char *buf;
+
+ buf = *buf_p;
+
+ if( string == NULL )
+ {
+ string = "(null)";
+ prec = -1;
+ }
+
+ if( prec >= 0 )
+ {
+ for( size = 0; size < prec; size++ )
+ {
+ if( string[ size ] == '\0' )
+ break;
+ }
+ }
+ else
+ size = strlen( string );
+
+ width -= size;
+
+ while( size-- )
+ *buf++ = *string++;
+
+ while( width-- > 0 )
+ *buf++ = ' ';
+
+ *buf_p = buf;
+}
+
+/*
+vsprintf
+
+I'm not going to support a bunch of the more arcane stuff in here
+just to keep it simpler. For example, the '*' and '$' are not
+currently supported. I've tried to make it so that it will just
+parse and ignore formats we don't support.
+*/
+int vsprintf( char *buffer, const char *fmt, va_list argptr )
+{
+ int *arg;
+ char *buf_p;
+ char ch;
+ int flags;
+ int width;
+ int prec;
+ int n;
+ char sign;
+
+ buf_p = buffer;
+ arg = (int *)argptr;
+
+ while( qtrue )
+ {
+ // run through the format string until we hit a '%' or '\0'
+ for( ch = *fmt; ( ch = *fmt ) != '\0' && ch != '%'; fmt++ )
+ *buf_p++ = ch;
+
+ if( ch == '\0' )
+ goto done;
+
+ // skip over the '%'
+ fmt++;
+
+ // reset formatting state
+ flags = 0;
+ width = 0;
+ prec = -1;
+ sign = '\0';
+
+rflag:
+ ch = *fmt++;
+reswitch:
+ switch( ch )
+ {
+ case '-':
+ flags |= LADJUST;
+ goto rflag;
+
+ case '.':
+ n = 0;
+ while( is_digit( ( ch = *fmt++ ) ) )
+ n = 10 * n + ( ch - '0' );
+
+ prec = n < 0 ? -1 : n;
+ goto reswitch;
+
+ case '0':
+ flags |= ZEROPAD;
+ goto rflag;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ n = 0;
+ do
+ {
+ n = 10 * n + ( ch - '0' );
+ ch = *fmt++;
+ } while( is_digit( ch ) );
+
+ width = n;
+ goto reswitch;
+
+ case 'c':
+ *buf_p++ = (char)*arg;
+ arg++;
+ break;
+
+ case 'u':
+ flags |= UNSIGNED;
+ case 'd':
+ case 'i':
+ AddInt( &buf_p, *arg, width, flags );
+ arg++;
+ break;
+
+ case 'f':
+ AddFloat( &buf_p, *(double *)arg, width, prec );
+#ifdef Q3_VM
+ arg += 1; // everything is 32 bit in my compiler
+#else
+ arg += 2;
+#endif
+ break;
+
+ case 's':
+ AddString( &buf_p, (char *)*arg, width, prec );
+ arg++;
+ break;
+
+ case 'v':
+ AddVec3_t( &buf_p, (vec_t *)*arg, width, prec );
+ arg++;
+ break;
+
+ case 'x':
+ flags |= HEX;
+ AddInt( &buf_p, *arg, width, prec );
+ arg++;
+ break;
+
+ case '%':
+ *buf_p++ = ch;
+ break;
+
+ default:
+ *buf_p++ = (char)*arg;
+ arg++;
+ break;
+ }
+ }
+
+done:
+ *buf_p = 0;
+ return buf_p - buffer;
+}
+
+
+/* this is really crappy */
+// FIXME: count is still inaccurate in some cases.
+int sscanf( const char *buffer, const char *fmt, ... )
+{
+ int cmd;
+ int **arg;
+ int count;
+
+ arg = (int **)&fmt + 1;
+ count = 0;
+
+ while( *fmt )
+ {
+ if( fmt[ 0 ] != '%' )
+ {
+ fmt++;
+ continue;
+ }
+
+ if( !buffer[ 0 ] ) break;
+
+ cmd = fmt[ 1 ];
+ fmt += 2;
+
+ switch( cmd )
+ {
+ case 'i':
+ case 'd':
+ case 'u':
+ **arg = _atoi( &buffer );
+ ++count;
+ break;
+ case 'f':
+ *(float *)*arg = _atof( &buffer );
+ ++count;
+ break;
+ case 'x':
+ **arg = _hextoi( &buffer );
+ ++count;
+ break;
+ }
+
+ arg++;
+ }
+
+ return count;
+}
+
+#endif
diff --git a/src/game/bg_lib.h b/src/game/bg_lib.h
new file mode 100644
index 0000000..962a625
--- /dev/null
+++ b/src/game/bg_lib.h
@@ -0,0 +1,120 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// bg_lib.h -- standard C library replacement routines used by code
+// compiled for the virtual machine
+
+// This file is NOT included on native builds
+#ifndef BG_LIB_H
+#define BG_LIB_H
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+typedef int size_t;
+
+typedef char * va_list;
+#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
+#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
+#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
+#define va_end(ap) ( ap = (va_list)0 )
+
+#define CHAR_BIT 8 /* number of bits in a char */
+#define SCHAR_MIN (-128) /* minimum signed char value */
+#define SCHAR_MAX 127 /* maximum signed char value */
+#define UCHAR_MAX 0xff /* maximum unsigned char value */
+
+#define SHRT_MIN (-32768) /* minimum (signed) short value */
+#define SHRT_MAX 32767 /* maximum (signed) short value */
+#define USHRT_MAX 0xffff /* maximum unsigned short value */
+#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */
+#define INT_MAX 2147483647 /* maximum (signed) int value */
+#define UINT_MAX 0xffffffff /* maximum unsigned int value */
+#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */
+#define LONG_MAX 2147483647L /* maximum (signed) long value */
+#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */
+
+#define isalnum(c) (isalpha(c) || isdigit(c))
+#define isalpha(c) (isupper(c) || islower(c))
+#define isascii(c) ((c) > 0 && (c) <= 0x7f)
+#define iscntrl(c) (((c) >= 0) && (((c) <= 0x1f) || ((c) == 0x7f)))
+#define isdigit(c) ((c) >= '0' && (c) <= '9')
+#define isgraph(c) ((c) != ' ' && isprint(c))
+#define islower(c) ((c) >= 'a' && (c) <= 'z')
+#define isprint(c) ((c) >= ' ' && (c) <= '~')
+#define ispunct(c) (((c) > ' ' && (c) <= '~') && !isalnum(c))
+#define isspace(c) ((c) == ' ' || (c) == '\f' || (c) == '\n' || (c) == '\r' || \
+ (c) == '\t' || (c) == '\v')
+#define isupper(c) ((c) >= 'A' && (c) <= 'Z')
+#define isxdigit(c) (isxupper(c) || isxlower(c))
+#define isxlower(c) (isdigit(c) || (c >= 'a' && c <= 'f'))
+#define isxupper(c) (isdigit(c) || (c >= 'A' && c <= 'F'))
+
+// Misc functions
+typedef int cmp_t( const void *, const void * );
+void qsort( void *a, size_t n, size_t es, cmp_t *cmp );
+void srand( unsigned seed );
+int rand( void );
+
+// String functions
+size_t strlen( const char *string );
+char *strcat( char *strDestination, const char *strSource );
+char *strcpy( char *strDestination, const char *strSource );
+int strcmp( const char *string1, const char *string2 );
+char *strchr( const char *string, int c );
+char *strrchr( const char *string, int c );
+char *strstr( const char *string, const char *strCharSet );
+char *strncpy( char *strDest, const char *strSource, size_t count );
+int tolower( int c );
+int toupper( int c );
+
+double atof( const char *string );
+double _atof( const char **stringPtr );
+int atoi( const char *string );
+int _atoi( const char **stringPtr );
+
+
+int vsprintf( char *buffer, const char *fmt, va_list argptr );
+int sscanf( const char *buffer, const char *fmt, ... );
+
+// Memory functions
+void *memmove( void *dest, const void *src, size_t count );
+void *memset( void *dest, int c, size_t count );
+void *memcpy( void *dest, const void *src, size_t count );
+
+// Math functions
+double ceil( double x );
+double floor( double x );
+double sqrt( double x );
+double sin( double x );
+double cos( double x );
+double atan2( double y, double x );
+double tan( double x );
+int abs( int n );
+double fabs( double x );
+double acos( double x );
+float pow( float x, float y );
+double rint( double v );
+
+#endif // BG_LIB_H
diff --git a/src/game/bg_local.h b/src/game/bg_local.h
new file mode 100644
index 0000000..354214c
--- /dev/null
+++ b/src/game/bg_local.h
@@ -0,0 +1,88 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// bg_local.h -- local definitions for the bg (both games) files
+
+#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes
+
+#define STEPSIZE 18
+
+#define TIMER_LAND 130
+#define TIMER_GESTURE (34*66+50)
+#define TIMER_ATTACK 500 //nonsegmented models
+
+#define OVERCLIP 1.001f
+
+#define FALLING_THRESHOLD -900.0f //what vertical speed to start falling sound at
+
+
+// all of the locals will be zeroed before each
+// pmove, just to make damn sure we don't have
+// any differences when running on client or server
+typedef struct
+{
+ vec3_t forward, right, up;
+ float frametime;
+
+ int msec;
+
+ qboolean walking;
+ qboolean groundPlane;
+ qboolean ladder;
+ trace_t groundTrace;
+
+ float impactSpeed;
+
+ vec3_t previous_origin;
+ vec3_t previous_velocity;
+ int previous_waterlevel;
+} pml_t;
+
+extern pmove_t *pm;
+extern pml_t pml;
+
+// movement parameters
+extern float pm_stopspeed;
+extern float pm_duckScale;
+extern float pm_swimScale;
+extern float pm_wadeScale;
+
+extern float pm_accelerate;
+extern float pm_airaccelerate;
+extern float pm_wateraccelerate;
+extern float pm_flyaccelerate;
+
+extern float pm_friction;
+extern float pm_waterfriction;
+extern float pm_flightfriction;
+
+extern int c_pmove;
+
+void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce );
+void PM_AddTouchEnt( int entityNum );
+void PM_AddEvent( int newEvent );
+
+qboolean PM_SlideMove( qboolean gravity );
+void PM_StepEvent( vec3_t from, vec3_t to, vec3_t normal );
+qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive );
+qboolean PM_PredictStepMove( void );
diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c
new file mode 100644
index 0000000..fd50509
--- /dev/null
+++ b/src/game/bg_misc.c
@@ -0,0 +1,5687 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// bg_misc.c -- both games misc functions, all completely stateless
+
+#include "../qcommon/q_shared.h"
+#include "bg_public.h"
+
+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 );
+void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t
+
+buildableAttributes_t bg_buildableList[ ] =
+{
+ {
+ BA_A_SPAWN, //int buildNum;
+ "eggpod", //char *buildName;
+ "Egg", //char *humanName;
+ "team_alien_spawn", //char *entityName;
+ { "models/buildables/eggpod/eggpod.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -15, -15, -15 }, //vec3_t mins;
+ { 15, 15, 15 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ ASPAWN_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ ASPAWN_HEALTH, //int health;
+ ASPAWN_REGEN, //int regenRate;
+ ASPAWN_SPLASHDAMAGE, //int splashDamage;
+ ASPAWN_SPLASHRADIUS, //int splashRadius;
+ MOD_ASPAWN, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ ASPAWN_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.5f, //float minNormal;
+ qtrue, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ ASPAWN_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_A_BARRICADE, //int buildNum;
+ "barricade", //char *buildName;
+ "Barricade", //char *humanName;
+ "team_alien_barricade",//char *entityName;
+ { "models/buildables/barricade/barricade.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -35, -35, -15 }, //vec3_t mins;
+ { 35, 35, 60 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ BARRICADE_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ BARRICADE_HEALTH, //int health;
+ BARRICADE_REGEN, //int regenRate;
+ BARRICADE_SPLASHDAMAGE,//int splashDamage;
+ BARRICADE_SPLASHRADIUS,//int splashRadius;
+ MOD_ASPAWN, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ BARRICADE_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.707f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qtrue, //qboolean creepTest;
+ BARRICADE_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replaceable;
+ },
+ {
+ BA_A_BOOSTER, //int buildNum;
+ "booster", //char *buildName;
+ "Booster", //char *humanName;
+ "team_alien_booster", //char *entityName;
+ { "models/buildables/booster/booster.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -26, -26, -9 }, //vec3_t mins;
+ { 26, 26, 9 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ BOOSTER_BP, //int buildPoints;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ BOOSTER_HEALTH, //int health;
+ BOOSTER_REGEN, //int regenRate;
+ BOOSTER_SPLASHDAMAGE, //int splashDamage;
+ BOOSTER_SPLASHRADIUS, //int splashRadius;
+ MOD_ASPAWN, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ BOOSTER_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.707f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qtrue, //qboolean creepTest;
+ BOOSTER_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qtrue, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qtrue, //qboolean replacable;
+ },
+ {
+ BA_A_ACIDTUBE, //int buildNum;
+ "acid_tube", //char *buildName;
+ "Acid Tube", //char *humanName;
+ "team_alien_acid_tube",//char *entityName;
+ { "models/buildables/acid_tube/acid_tube.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -25, -25, -25 }, //vec3_t mins;
+ { 25, 25, 25 }, //vec3_t maxs;
+ -15.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ ACIDTUBE_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ ACIDTUBE_HEALTH, //int health;
+ ACIDTUBE_REGEN, //int regenRate;
+ ACIDTUBE_SPLASHDAMAGE, //int splashDamage;
+ ACIDTUBE_SPLASHRADIUS, //int splashRadius;
+ MOD_ATUBE, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 200, //int nextthink;
+ ACIDTUBE_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.0f, //float minNormal;
+ qtrue, //qboolean invertNormal;
+ qtrue, //qboolean creepTest;
+ ACIDTUBE_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_A_HIVE, //int buildNum;
+ "hive", //char *buildName;
+ "Hive", //char *humanName;
+ "team_alien_hive", //char *entityName;
+ { "models/buildables/acid_tube/acid_tube.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -35, -35, -25 }, //vec3_t mins;
+ { 35, 35, 25 }, //vec3_t maxs;
+ -15.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ HIVE_BP, //int buildPoints;
+ ( 1 << S3 ), //int stages
+ HIVE_HEALTH, //int health;
+ HIVE_REGEN, //int regenRate;
+ HIVE_SPLASHDAMAGE, //int splashDamage;
+ HIVE_SPLASHRADIUS, //int splashRadius;
+ MOD_ASPAWN, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 500, //int nextthink;
+ HIVE_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_HIVE, //weapon_t turretProjType;
+ 0.0f, //float minNormal;
+ qtrue, //qboolean invertNormal;
+ qtrue, //qboolean creepTest;
+ HIVE_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_A_TRAPPER, //int buildNum;
+ "trapper", //char *buildName;
+ "Trapper", //char *humanName;
+ "team_alien_trapper", //char *entityName;
+ { "models/buildables/trapper/trapper.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -15, -15, -15 }, //vec3_t mins;
+ { 15, 15, 15 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ TRAPPER_BP, //int buildPoints;
+ ( 1 << S2 )|( 1 << S3 ), //int stages //NEEDS ADV BUILDER SO S2 AND UP
+ TRAPPER_HEALTH, //int health;
+ TRAPPER_REGEN, //int regenRate;
+ TRAPPER_SPLASHDAMAGE, //int splashDamage;
+ TRAPPER_SPLASHRADIUS, //int splashRadius;
+ MOD_ASPAWN, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ TRAPPER_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ TRAPPER_RANGE, //int turretRange;
+ TRAPPER_REPEAT, //int turretFireSpeed;
+ WP_LOCKBLOB_LAUNCHER, //weapon_t turretProjType;
+ 0.0f, //float minNormal;
+ qtrue, //qboolean invertNormal;
+ qtrue, //qboolean creepTest;
+ TRAPPER_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qtrue, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_A_OVERMIND, //int buildNum;
+ "overmind", //char *buildName;
+ "Overmind", //char *humanName;
+ "team_alien_overmind", //char *entityName;
+ { "models/buildables/overmind/overmind.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -45, -45, -15 }, //vec3_t mins;
+ { 45, 45, 95 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ OVERMIND_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ OVERMIND_HEALTH, //int health;
+ OVERMIND_REGEN, //int regenRate;
+ OVERMIND_SPLASHDAMAGE, //int splashDamage;
+ OVERMIND_SPLASHRADIUS, //int splashRadius;
+ MOD_ASPAWN, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ OVERMIND_ATTACK_REPEAT,//int nextthink;
+ OVERMIND_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ OVERMIND_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qtrue, //qboolean reactorTest;
+ qtrue, //qboolean replacable;
+ },
+ {
+ BA_A_HOVEL, //int buildNum;
+ "hovel", //char *buildName;
+ "Hovel", //char *humanName;
+ "team_alien_hovel", //char *entityName;
+ { "models/buildables/hovel/hovel.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -50, -50, -20 }, //vec3_t mins;
+ { 50, 50, 20 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ HOVEL_BP, //int buildPoints;
+ ( 1 << S3 ), //int stages
+ HOVEL_HEALTH, //int health;
+ HOVEL_REGEN, //int regenRate;
+ HOVEL_SPLASHDAMAGE, //int splashDamage;
+ HOVEL_SPLASHRADIUS, //int splashRadius;
+ MOD_ASPAWN, //int meansOfDeath;
+ BIT_ALIENS, //int team;
+ ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 150, //int nextthink;
+ HOVEL_BT, //int buildTime;
+ qtrue, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qtrue, //qboolean creepTest;
+ HOVEL_CREEPSIZE, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qtrue, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_H_SPAWN, //int buildNum;
+ "telenode", //char *buildName;
+ "Telenode", //char *humanName;
+ "team_human_spawn", //char *entityName;
+ { "models/buildables/telenode/telenode.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -40, -40, -4 }, //vec3_t mins;
+ { 40, 40, 4 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ HSPAWN_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ HSPAWN_HEALTH, //int health;
+ 0, //int regenRate;
+ HSPAWN_SPLASHDAMAGE, //int splashDamage;
+ HSPAWN_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ HSPAWN_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qtrue, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_H_MEDISTAT, //int buildNum;
+ "medistat", //char *buildName;
+ "Medistation", //char *humanName;
+ "team_human_medistat", //char *entityName;
+ { "models/buildables/medistat/medistat.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -35, -35, -7 }, //vec3_t mins;
+ { 35, 35, 7 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ MEDISTAT_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ MEDISTAT_HEALTH, //int health;
+ 0, //int regenRate;
+ MEDISTAT_SPLASHDAMAGE, //int splashDamage;
+ MEDISTAT_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ MEDISTAT_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qtrue, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qtrue, //qboolean replacable;
+ },
+ {
+ BA_H_MGTURRET, //int buildNum;
+ "mgturret", //char *buildName;
+ "Machinegun Turret", //char *humanName;
+ "team_human_mgturret", //char *entityName;
+ { "models/buildables/mgturret/turret_base.md3",
+ "models/buildables/mgturret/turret_barrel.md3",
+ "models/buildables/mgturret/turret_top.md3", 0 },
+ 1.0f, //float modelScale;
+ { -25, -25, -20 }, //vec3_t mins;
+ { 25, 25, 20 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ MGTURRET_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ MGTURRET_HEALTH, //int health;
+ 0, //int regenRate;
+ MGTURRET_SPLASHDAMAGE, //int splashDamage;
+ MGTURRET_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 50, //int nextthink;
+ MGTURRET_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ MGTURRET_RANGE, //int turretRange;
+ MGTURRET_REPEAT, //int turretFireSpeed;
+ WP_MGTURRET, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qtrue, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_H_TESLAGEN, //int buildNum;
+ "tesla", //char *buildName;
+ "Tesla Generator", //char *humanName;
+ "team_human_tesla", //char *entityName;
+ { "models/buildables/tesla/tesla.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -22, -22, -40 }, //vec3_t mins;
+ { 22, 22, 40 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ TESLAGEN_BP, //int buildPoints;
+ ( 1 << S3 ), //int stages
+ TESLAGEN_HEALTH, //int health;
+ 0, //int regenRate;
+ TESLAGEN_SPLASHDAMAGE, //int splashDamage;
+ TESLAGEN_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 150, //int nextthink;
+ TESLAGEN_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ TESLAGEN_RANGE, //int turretRange;
+ TESLAGEN_REPEAT, //int turretFireSpeed;
+ WP_TESLAGEN, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qtrue, //qboolean dccTest;
+ qtrue, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qfalse, //qboolean replacable;
+ },
+ {
+ BA_H_DCC, //int buildNum;
+ "dcc", //char *buildName;
+ "Defence Computer", //char *humanName;
+ "team_human_dcc", //char *entityName;
+ { "models/buildables/dcc/dcc.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -35, -35, -13 }, //vec3_t mins;
+ { 35, 35, 47 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ DC_BP, //int buildPoints;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ DC_HEALTH, //int health;
+ 0, //int regenRate;
+ DC_SPLASHDAMAGE, //int splashDamage;
+ DC_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ DC_BT, //int buildTime;
+ qfalse, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qtrue, //qboolean replacable;
+ },
+ {
+ BA_H_ARMOURY, //int buildNum;
+ "arm", //char *buildName;
+ "Armoury", //char *humanName;
+ "team_human_armoury", //char *entityName;
+ { "models/buildables/arm/arm.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -40, -40, -13 }, //vec3_t mins;
+ { 40, 40, 50 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ ARMOURY_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ ARMOURY_HEALTH, //int health;
+ 0, //int regenRate;
+ ARMOURY_SPLASHDAMAGE, //int splashDamage;
+ ARMOURY_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ ARMOURY_BT, //int buildTime;
+ qtrue, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qtrue, //qboolean replacable;
+ },
+ {
+ BA_H_REACTOR, //int buildNum;
+ "reactor", //char *buildName;
+ "Reactor", //char *humanName;
+ "team_human_reactor", //char *entityName;
+ { "models/buildables/reactor/reactor.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -50, -50, -15 }, //vec3_t mins;
+ { 50, 50, 95 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ REACTOR_BP, //int buildPoints;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ REACTOR_HEALTH, //int health;
+ 0, //int regenRate;
+ REACTOR_SPLASHDAMAGE, //int splashDamage;
+ REACTOR_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ REACTOR_ATTACK_REPEAT, //int nextthink;
+ REACTOR_BT, //int buildTime;
+ qtrue, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qtrue, //qboolean reactorTest;
+ qtrue, //qboolean replacable;
+ },
+ {
+ BA_H_REPEATER, //int buildNum;
+ "repeater", //char *buildName;
+ "Repeater", //char *humanName;
+ "team_human_repeater", //char *entityName;
+ { "models/buildables/repeater/repeater.md3", 0, 0, 0 },
+ 1.0f, //float modelScale;
+ { -15, -15, -15 }, //vec3_t mins;
+ { 15, 15, 25 }, //vec3_t maxs;
+ 0.0f, //float zOffset;
+ TR_GRAVITY, //trType_t traj;
+ 0.0, //float bounce;
+ REPEATER_BP, //int buildPoints;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ REPEATER_HEALTH, //int health;
+ 0, //int regenRate;
+ REPEATER_SPLASHDAMAGE, //int splashDamage;
+ REPEATER_SPLASHRADIUS, //int splashRadius;
+ MOD_HSPAWN, //int meansOfDeath;
+ BIT_HUMANS, //int team;
+ ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
+ BANIM_IDLE1, //int idleAnim;
+ 100, //int nextthink;
+ REPEATER_BT, //int buildTime;
+ qtrue, //qboolean usable;
+ 0, //int turretRange;
+ 0, //int turretFireSpeed;
+ WP_NONE, //weapon_t turretProjType;
+ 0.95f, //float minNormal;
+ qfalse, //qboolean invertNormal;
+ qfalse, //qboolean creepTest;
+ 0, //int creepSize;
+ qfalse, //qboolean dccTest;
+ qfalse, //qboolean transparentTest;
+ qfalse, //qboolean reactorTest;
+ qtrue, //qboolean replacable;
+ }
+};
+
+int bg_numBuildables = sizeof( bg_buildableList ) / sizeof( bg_buildableList[ 0 ] );
+
+//separate from bg_buildableList to work around char struct init bug
+buildableAttributeOverrides_t bg_buildableOverrideList[ BA_NUM_BUILDABLES ];
+
+/*
+==============
+BG_FindBuildNumForName
+==============
+*/
+int BG_FindBuildNumForName( char *name )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( !Q_stricmp( bg_buildableList[ i ].buildName, name ) )
+ return bg_buildableList[ i ].buildNum;
+ }
+
+ //wimp out
+ return BA_NONE;
+}
+
+/*
+==============
+BG_FindBuildNumForEntityName
+==============
+*/
+int BG_FindBuildNumForEntityName( char *name )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( !Q_stricmp( bg_buildableList[ i ].entityName, name ) )
+ return bg_buildableList[ i ].buildNum;
+ }
+
+ //wimp out
+ return BA_NONE;
+}
+
+/*
+==============
+BG_FindNameForBuildNum
+==============
+*/
+char *BG_FindNameForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ return bg_buildableList[ i ].buildName;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindHumanNameForBuildNum
+==============
+*/
+char *BG_FindHumanNameForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ return bg_buildableList[ i ].humanName;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindEntityNameForBuildNum
+==============
+*/
+char *BG_FindEntityNameForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ return bg_buildableList[ i ].entityName;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindModelsForBuildNum
+==============
+*/
+char *BG_FindModelsForBuildable( int bclass, int modelNum )
+{
+ int i;
+
+ if( bg_buildableOverrideList[ bclass ].models[ modelNum ][ 0 ] != 0 )
+ return bg_buildableOverrideList[ bclass ].models[ modelNum ];
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ return bg_buildableList[ i ].models[ modelNum ];
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindModelScaleForBuildable
+==============
+*/
+float BG_FindModelScaleForBuildable( int bclass )
+{
+ int i;
+
+ if( bg_buildableOverrideList[ bclass ].modelScale != 0.0f )
+ return bg_buildableOverrideList[ bclass ].modelScale;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ return bg_buildableList[ i ].modelScale;
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelScaleForBuildable( %d )\n", bclass );
+ return 1.0f;
+}
+
+/*
+==============
+BG_FindBBoxForBuildable
+==============
+*/
+void BG_FindBBoxForBuildable( int bclass, vec3_t mins, vec3_t maxs )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ if( mins != NULL )
+ {
+ VectorCopy( bg_buildableList[ i ].mins, mins );
+
+ if( VectorLength( bg_buildableOverrideList[ bclass ].mins ) )
+ VectorCopy( bg_buildableOverrideList[ bclass ].mins, mins );
+ }
+
+ if( maxs != NULL )
+ {
+ VectorCopy( bg_buildableList[ i ].maxs, maxs );
+
+ if( VectorLength( bg_buildableOverrideList[ bclass ].maxs ) )
+ VectorCopy( bg_buildableOverrideList[ bclass ].maxs, maxs );
+ }
+
+ return;
+ }
+ }
+
+ if( mins != NULL )
+ VectorCopy( bg_buildableList[ 0 ].mins, mins );
+
+ if( maxs != NULL )
+ VectorCopy( bg_buildableList[ 0 ].maxs, maxs );
+}
+
+/*
+==============
+BG_FindZOffsetForBuildable
+==============
+*/
+float BG_FindZOffsetForBuildable( int bclass )
+{
+ int i;
+
+ if( bg_buildableOverrideList[ bclass ].zOffset != 0.0f )
+ return bg_buildableOverrideList[ bclass ].zOffset;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].zOffset;
+ }
+ }
+
+ return 0.0f;
+}
+
+/*
+==============
+BG_FindTrajectoryForBuildable
+==============
+*/
+trType_t BG_FindTrajectoryForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].traj;
+ }
+ }
+
+ return TR_GRAVITY;
+}
+
+/*
+==============
+BG_FindBounceForBuildable
+==============
+*/
+float BG_FindBounceForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].bounce;
+ }
+ }
+
+ return 0.0;
+}
+
+/*
+==============
+BG_FindBuildPointsForBuildable
+==============
+*/
+int BG_FindBuildPointsForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].buildPoints;
+ }
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindStagesForBuildable
+==============
+*/
+qboolean BG_FindStagesForBuildable( int bclass, stage_t stage )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ if( bg_buildableList[ i ].stages & ( 1 << stage ) )
+ return qtrue;
+ else
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindHealthForBuildable
+==============
+*/
+int BG_FindHealthForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].health;
+ }
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindRegenRateForBuildable
+==============
+*/
+int BG_FindRegenRateForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].regenRate;
+ }
+ }
+
+ return 0;
+}
+
+/*
+==============
+BG_FindSplashDamageForBuildable
+==============
+*/
+int BG_FindSplashDamageForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].splashDamage;
+ }
+ }
+
+ return 50;
+}
+
+/*
+==============
+BG_FindSplashRadiusForBuildable
+==============
+*/
+int BG_FindSplashRadiusForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].splashRadius;
+ }
+ }
+
+ return 200;
+}
+
+/*
+==============
+BG_FindMODForBuildable
+==============
+*/
+int BG_FindMODForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].meansOfDeath;
+ }
+ }
+
+ return MOD_UNKNOWN;
+}
+
+/*
+==============
+BG_FindTeamForBuildable
+==============
+*/
+int BG_FindTeamForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].team;
+ }
+ }
+
+ return BIT_NONE;
+}
+
+/*
+==============
+BG_FindBuildWeaponForBuildable
+==============
+*/
+weapon_t BG_FindBuildWeaponForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].buildWeapon;
+ }
+ }
+
+ return WP_NONE;
+}
+
+/*
+==============
+BG_FindAnimForBuildable
+==============
+*/
+int BG_FindAnimForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].idleAnim;
+ }
+ }
+
+ return BANIM_IDLE1;
+}
+
+/*
+==============
+BG_FindNextThinkForBuildable
+==============
+*/
+int BG_FindNextThinkForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].nextthink;
+ }
+ }
+
+ return 100;
+}
+
+/*
+==============
+BG_FindBuildTimeForBuildable
+==============
+*/
+int BG_FindBuildTimeForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].buildTime;
+ }
+ }
+
+ return 10000;
+}
+
+/*
+==============
+BG_FindUsableForBuildable
+==============
+*/
+qboolean BG_FindUsableForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].usable;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindFireSpeedForBuildable
+==============
+*/
+int BG_FindFireSpeedForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].turretFireSpeed;
+ }
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindRangeForBuildable
+==============
+*/
+int BG_FindRangeForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].turretRange;
+ }
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindProjTypeForBuildable
+==============
+*/
+weapon_t BG_FindProjTypeForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].turretProjType;
+ }
+ }
+
+ return WP_NONE;
+}
+
+/*
+==============
+BG_FindMinNormalForBuildable
+==============
+*/
+float BG_FindMinNormalForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].minNormal;
+ }
+ }
+
+ return 0.707f;
+}
+
+/*
+==============
+BG_FindInvertNormalForBuildable
+==============
+*/
+qboolean BG_FindInvertNormalForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].invertNormal;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindCreepTestForBuildable
+==============
+*/
+int BG_FindCreepTestForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].creepTest;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindCreepSizeForBuildable
+==============
+*/
+int BG_FindCreepSizeForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].creepSize;
+ }
+ }
+
+ return CREEP_BASESIZE;
+}
+
+/*
+==============
+BG_FindDCCTestForBuildable
+==============
+*/
+int BG_FindDCCTestForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].dccTest;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindUniqueTestForBuildable
+==============
+*/
+int BG_FindUniqueTestForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].reactorTest;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindReplaceableTestForBuildable
+==============
+*/
+qboolean BG_FindReplaceableTestForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].replaceable;
+ }
+ }
+ return qfalse;
+}
+
+/*
+==============
+BG_FindOverrideForBuildable
+==============
+*/
+static buildableAttributeOverrides_t *BG_FindOverrideForBuildable( int bclass )
+{
+ return &bg_buildableOverrideList[ bclass ];
+}
+
+/*
+==============
+BG_FindTransparentTestForBuildable
+==============
+*/
+qboolean BG_FindTransparentTestForBuildable( int bclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numBuildables; i++ )
+ {
+ if( bg_buildableList[ i ].buildNum == bclass )
+ {
+ return bg_buildableList[ i ].transparentTest;
+ }
+ }
+ return qfalse;
+}
+
+/*
+======================
+BG_ParseBuildableFile
+
+Parses a configuration file describing a builable
+======================
+*/
+static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeOverrides_t *bao )
+{
+ char *text_p;
+ int i;
+ int len;
+ char *token;
+ char text[ 20000 ];
+ fileHandle_t f;
+ float scale;
+
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len < 0 )
+ return qfalse;
+
+ if( len == 0 || len >= sizeof( text ) - 1 )
+ {
+ trap_FS_FCloseFile( f );
+ Com_Printf( S_COLOR_RED "ERROR: Buildable file %s is %s\n", filename,
+ len == 0 ? "empty" : "too long" );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "model" ) )
+ {
+ int index = 0;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ index = atoi( token );
+
+ if( index < 0 )
+ index = 0;
+ else if( index > 3 )
+ index = 3;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( bao->models[ index ], token, sizeof( bao->models[ 0 ] ) );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "modelScale" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ scale = atof( token );
+
+ if( scale < 0.0f )
+ scale = 0.0f;
+
+ bao->modelScale = scale;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "mins" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ bao->mins[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "maxs" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ bao->maxs[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "zOffset" ) )
+ {
+ float offset;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ offset = atof( token );
+
+ bao->zOffset = offset;
+
+ continue;
+ }
+
+
+ Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+BG_InitBuildableOverrides
+
+Set any overrides specfied by file
+===============
+*/
+void BG_InitBuildableOverrides( void )
+{
+ int i;
+ buildableAttributeOverrides_t *bao;
+
+ for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ bao = BG_FindOverrideForBuildable( i );
+
+ BG_ParseBuildableFile( va( "overrides/buildables/%s.cfg", BG_FindNameForBuildable( i ) ), bao );
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+classAttributes_t bg_classList[ ] =
+{
+ {
+ PCL_NONE, //int classnum;
+ "spectator", //char *className;
+ "Spectator", //char *humanName;
+ "", //char *modelname;
+ 1.0f, //float modelScale;
+ "", //char *skinname;
+ 1.0f, //float shadowScale;
+ "", //char *hudname;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ { -15, -15, -15 }, //vec3_t mins;
+ { 15, 15, 15 }, //vec3_t maxs;
+ { 15, 15, 15 }, //vec3_t crouchmaxs;
+ { -15, -15, -15 }, //vec3_t deadmins;
+ { 15, 15, 15 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 0, 0, //int viewheight, crouchviewheight;
+ 0, //int health;
+ 0.0f, //float fallDamage;
+ 0, //int regenRate;
+ 0, //int abilities;
+ WP_NONE, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 90, //int fov;
+ 0.000f, //float bob;
+ 1.0f, //float bobCycle;
+ 0, //int steptime;
+ 600, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 270.0f, //float jumpMagnitude;
+ 1.0f, //float knockbackScale;
+ { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ 0, //int cost;
+ 0 //int value;
+ },
+ {
+ PCL_ALIEN_BUILDER0, //int classnum;
+ "builder", //char *className;
+ "Builder", //char *humanName;
+ "builder", //char *modelname;
+ 1.0f, //float modelScale;
+ "default", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_builder_hud", //char *hudname;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ { -15, -15, -20 }, //vec3_t mins;
+ { 15, 15, 20 }, //vec3_t maxs;
+ { 15, 15, 20 }, //vec3_t crouchmaxs;
+ { -15, -15, -4 }, //vec3_t deadmins;
+ { 15, 15, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 0, 0, //int viewheight, crouchviewheight;
+ ABUILDER_HEALTH, //int health;
+ 0.2f, //float fallDamage;
+ ABUILDER_REGEN, //int regenRate;
+ SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_ALIENSENSE,//int abilities;
+ WP_ABUILD, //weapon_t startWeapon
+ 95.0f, //float buildDist;
+ 80, //int fov;
+ 0.001f, //float bob;
+ 2.0f, //float bobCycle;
+ 150, //int steptime;
+ ABUILDER_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 195.0f, //float jumpMagnitude;
+ 1.0f, //float knockbackScale;
+ { PCL_ALIEN_BUILDER0_UPG, PCL_ALIEN_LEVEL0, PCL_NONE }, //int children[ 3 ];
+ ABUILDER_COST, //int cost;
+ ABUILDER_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_BUILDER0_UPG, //int classnum;
+ "builderupg", //char *classname;
+ "Advanced Builder", //char *humanname;
+ "builder", //char *modelname;
+ 1.0f, //float modelScale;
+ "advanced", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_builder_hud", //char *hudname;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ { -20, -20, -20 }, //vec3_t mins;
+ { 20, 20, 20 }, //vec3_t maxs;
+ { 20, 20, 20 }, //vec3_t crouchmaxs;
+ { -20, -20, -4 }, //vec3_t deadmins;
+ { 20, 20, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 0, 0, //int viewheight, crouchviewheight;
+ ABUILDER_UPG_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ ABUILDER_UPG_REGEN, //int regenRate;
+ SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities;
+ WP_ABUILD2, //weapon_t startWeapon
+ 105.0f, //float buildDist;
+ 110, //int fov;
+ 0.001f, //float bob;
+ 2.0f, //float bobCycle;
+ 100, //int steptime;
+ ABUILDER_UPG_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 270.0f, //float jumpMagnitude;
+ 1.0f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL0, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ ABUILDER_UPG_COST, //int cost;
+ ABUILDER_UPG_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL0, //int classnum;
+ "level0", //char *classname;
+ "Soldier", //char *humanname;
+ "jumper", //char *modelname;
+ 0.2f, //float modelScale;
+ "default", //char *skinname;
+ 0.3f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ { -15, -15, -15 }, //vec3_t mins;
+ { 15, 15, 15 }, //vec3_t maxs;
+ { 15, 15, 15 }, //vec3_t crouchmaxs;
+ { -15, -15, -4 }, //vec3_t deadmins;
+ { 15, 15, 4 }, //vec3_t deadmaxs;
+ -8.0f, //float zOffset
+ 0, 0, //int viewheight, crouchviewheight;
+ LEVEL0_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL0_REGEN, //int regenRate;
+ SCA_WALLCLIMBER|SCA_NOWEAPONDRIFT|
+ SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL0, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 140, //int fov;
+ 0.0f, //float bob;
+ 2.5f, //float bobCycle;
+ 25, //int steptime;
+ LEVEL0_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 400.0f, //float stopSpeed;
+ 250.0f, //float jumpMagnitude;
+ 2.0f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL1, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ LEVEL0_COST, //int cost;
+ LEVEL0_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL1, //int classnum;
+ "level1", //char *classname;
+ "Hydra", //char *humanname;
+ "spitter", //char *modelname;
+ 0.6f, //float modelScale;
+ "default", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ { -18, -18, -18 }, //vec3_t mins;
+ { 18, 18, 18 }, //vec3_t maxs;
+ { 18, 18, 18 }, //vec3_t crouchmaxs;
+ { -18, -18, -4 }, //vec3_t deadmins;
+ { 18, 18, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 0, 0, //int viewheight, crouchviewheight;
+ LEVEL1_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL1_REGEN, //int regenRate;
+ SCA_NOWEAPONDRIFT|
+ SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL1, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 120, //int fov;
+ 0.001f, //float bob;
+ 1.8f, //float bobCycle;
+ 60, //int steptime;
+ LEVEL1_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 300.0f, //float stopSpeed;
+ 270.0f, //float jumpMagnitude;
+ 1.2f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL2, PCL_ALIEN_LEVEL1_UPG, PCL_NONE }, //int children[ 3 ];
+ LEVEL1_COST, //int cost;
+ LEVEL1_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL1_UPG, //int classnum;
+ "level1upg", //char *classname;
+ "Hydra Upgrade", //char *humanname;
+ "spitter", //char *modelname;
+ 0.7f, //float modelScale;
+ "blue", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ { -20, -20, -20 }, //vec3_t mins;
+ { 20, 20, 20 }, //vec3_t maxs;
+ { 20, 20, 20 }, //vec3_t crouchmaxs;
+ { -20, -20, -4 }, //vec3_t deadmins;
+ { 20, 20, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 0, 0, //int viewheight, crouchviewheight;
+ LEVEL1_UPG_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL1_UPG_REGEN, //int regenRate;
+ SCA_NOWEAPONDRIFT|SCA_FOVWARPS|
+ SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL1_UPG, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 120, //int fov;
+ 0.001f, //float bob;
+ 1.8f, //float bobCycle;
+ 60, //int steptime;
+ LEVEL1_UPG_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 300.0f, //float stopSpeed;
+ 270.0f, //float jumpMagnitude;
+ 1.1f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL2, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ LEVEL1_UPG_COST, //int cost;
+ LEVEL1_UPG_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL2, //int classnum;
+ "level2", //char *classname;
+ "Chimera", //char *humanname;
+ "tarantula", //char *modelname;
+ 0.75f, //float modelScale;
+ "default", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ { -22, -22, -22 }, //vec3_t mins;
+ { 22, 22, 22 }, //vec3_t maxs;
+ { 22, 22, 22 }, //vec3_t crouchmaxs;
+ { -22, -22, -4 }, //vec3_t deadmins;
+ { 22, 22, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 10, 10, //int viewheight, crouchviewheight;
+ LEVEL2_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL2_REGEN, //int regenRate;
+ SCA_NOWEAPONDRIFT|SCA_WALLJUMPER|
+ SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL2, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 90, //int fov;
+ 0.001f, //float bob;
+ 1.5f, //float bobCycle;
+ 80, //int steptime;
+ LEVEL2_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 2.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 400.0f, //float jumpMagnitude;
+ 0.8f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL3, PCL_ALIEN_LEVEL2_UPG, PCL_NONE }, //int children[ 3 ];
+ LEVEL2_COST, //int cost;
+ LEVEL2_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL2_UPG, //int classnum;
+ "level2upg", //char *classname;
+ "Chimera Upgrade", //char *humanname;
+ "tarantula", //char *modelname;
+ 0.9f, //float modelScale;
+ "red", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ { -24, -24, -24 }, //vec3_t mins;
+ { 24, 24, 24 }, //vec3_t maxs;
+ { 24, 24, 24 }, //vec3_t crouchmaxs;
+ { -24, -24, -4 }, //vec3_t deadmins;
+ { 24, 24, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 12, 12, //int viewheight, crouchviewheight;
+ LEVEL2_UPG_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL2_UPG_REGEN, //int regenRate;
+ SCA_NOWEAPONDRIFT|SCA_WALLJUMPER|
+ SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL2_UPG, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 90, //int fov;
+ 0.001f, //float bob;
+ 1.5f, //float bobCycle;
+ 80, //int steptime;
+ LEVEL2_UPG_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 2.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 400.0f, //float jumpMagnitude;
+ 0.7f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL3, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ LEVEL2_UPG_COST, //int cost;
+ LEVEL2_UPG_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL3, //int classnum;
+ "level3", //char *classname;
+ "Dragoon", //char *humanname;
+ "prowl", //char *modelname;
+ 1.0f, //float modelScale;
+ "default", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ { -32, -32, -21 }, //vec3_t mins;
+ { 32, 32, 21 }, //vec3_t maxs;
+ { 32, 32, 21 }, //vec3_t crouchmaxs;
+ { -32, -32, -4 }, //vec3_t deadmins;
+ { 32, 32, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 24, 24, //int viewheight, crouchviewheight;
+ LEVEL3_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL3_REGEN, //int regenRate;
+ SCA_NOWEAPONDRIFT|
+ SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL3, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 110, //int fov;
+ 0.0005f, //float bob;
+ 1.3f, //float bobCycle;
+ 90, //int steptime;
+ LEVEL3_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 200.0f, //float stopSpeed;
+ 270.0f, //float jumpMagnitude;
+ 0.5f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL4, PCL_ALIEN_LEVEL3_UPG, PCL_NONE }, //int children[ 3 ];
+ LEVEL3_COST, //int cost;
+ LEVEL3_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL3_UPG, //int classnum;
+ "level3upg", //char *classname;
+ "Dragoon Upgrade", //char *humanname;
+ "prowl", //char *modelname;
+ 1.0f, //float modelScale;
+ "default", //char *skinname;
+ 1.0f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S3 ), //int stages
+ { -32, -32, -21 }, //vec3_t mins;
+ { 32, 32, 21 }, //vec3_t maxs;
+ { 32, 32, 21 }, //vec3_t crouchmaxs;
+ { -32, -32, -4 }, //vec3_t deadmins;
+ { 32, 32, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 27, 27, //int viewheight, crouchviewheight;
+ LEVEL3_UPG_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL3_UPG_REGEN, //int regenRate;
+ SCA_NOWEAPONDRIFT|
+ SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL3_UPG, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 110, //int fov;
+ 0.0005f, //float bob;
+ 1.3f, //float bobCycle;
+ 90, //int steptime;
+ LEVEL3_UPG_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 200.0f, //float stopSpeed;
+ 270.0f, //float jumpMagnitude;
+ 0.4f, //float knockbackScale;
+ { PCL_ALIEN_LEVEL4, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ LEVEL3_UPG_COST, //int cost;
+ LEVEL3_UPG_VALUE //int value;
+ },
+ {
+ PCL_ALIEN_LEVEL4, //int classnum;
+ "level4", //char *classname;
+ "Big Mofo", //char *humanname;
+ "mofo", //char *modelname;
+ 1.0f, //float modelScale;
+ "default", //char *skinname;
+ 2.0f, //float shadowScale;
+ "alien_general_hud", //char *hudname;
+ ( 1 << S3 ), //int stages
+ { -30, -30, -20 }, //vec3_t mins;
+ { 30, 30, 20 }, //vec3_t maxs;
+ { 30, 30, 20 }, //vec3_t crouchmaxs;
+ { -15, -15, -4 }, //vec3_t deadmins;
+ { 15, 15, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 35, 35, //int viewheight, crouchviewheight;
+ LEVEL4_HEALTH, //int health;
+ 0.0f, //float fallDamage;
+ LEVEL4_REGEN, //int regenRate;
+ SCA_NOWEAPONDRIFT|
+ SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities;
+ WP_ALEVEL4, //weapon_t startWeapon
+ 0.0f, //float buildDist;
+ 90, //int fov;
+ 0.001f, //float bob;
+ 1.1f, //float bobCycle;
+ 100, //int steptime;
+ LEVEL4_SPEED, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 170.0f, //float jumpMagnitude;
+ 0.1f, //float knockbackScale;
+ { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ LEVEL4_COST, //int cost;
+ LEVEL4_VALUE //int value;
+ },
+ {
+ PCL_HUMAN, //int classnum;
+ "human_base", //char *classname;
+ "Human", //char *humanname;
+ "sarge", //char *modelname;
+ 1.0f, //float modelScale;
+ "default", //char *skinname;
+ 1.0f, //float shadowScale;
+ "human_hud", //char *hudname;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ { -15, -15, -24 }, //vec3_t mins;
+ { 15, 15, 32 }, //vec3_t maxs;
+ { 15, 15, 16 }, //vec3_t crouchmaxs;
+ { -15, -15, -4 }, //vec3_t deadmins;
+ { 15, 15, 4 }, //vec3_t deadmaxs;
+ 0.0f, //float zOffset
+ 26, 12, //int viewheight, crouchviewheight;
+ 100, //int health;
+ 1.0f, //float fallDamage;
+ 0, //int regenRate;
+ SCA_TAKESFALLDAMAGE|
+ SCA_CANUSELADDERS, //int abilities;
+ WP_NONE, //special-cased in g_client.c //weapon_t startWeapon
+ 110.0f, //float buildDist;
+ 90, //int fov;
+ 0.002f, //float bob;
+ 1.0f, //float bobCycle;
+ 100, //int steptime;
+ 1.0f, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 220.0f, //float jumpMagnitude;
+ 1.0f, //float knockbackScale;
+ { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ 0, //int cost;
+ 0 //int value;
+ },
+ {
+ PCL_HUMAN_BSUIT, //int classnum;
+ "human_bsuit", //char *classname;
+ "bsuit", //char *humanname;
+ "keel", //char *modelname;
+ 1.0f, //float modelScale;
+ "default", //char *skinname;
+ 1.0f, //float shadowScale;
+ "human_hud", //char *hudname;
+ ( 1 << S3 ), //int stages
+ { -15, -15, -38 }, //vec3_t mins;
+ { 15, 15, 38 }, //vec3_t maxs;
+ { 15, 15, 38 }, //vec3_t crouchmaxs;
+ { -15, -15, -4 }, //vec3_t deadmins;
+ { 15, 15, 4 }, //vec3_t deadmaxs;
+ -16.0f, //float zOffset
+ 35, 35, //int viewheight, crouchviewheight;
+ 100, //int health;
+ 1.0f, //float fallDamage;
+ 0, //int regenRate;
+ SCA_TAKESFALLDAMAGE|
+ SCA_CANUSELADDERS, //int abilities;
+ WP_NONE, //special-cased in g_client.c //weapon_t startWeapon
+ 110.0f, //float buildDist;
+ 90, //int fov;
+ 0.002f, //float bob;
+ 1.0f, //float bobCycle;
+ 100, //int steptime;
+ 1.0f, //float speed;
+ 10.0f, //float acceleration;
+ 1.0f, //float airAcceleration;
+ 6.0f, //float friction;
+ 100.0f, //float stopSpeed;
+ 270.0f, //float jumpMagnitude;
+ 1.0f, //float knockbackScale;
+ { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ];
+ 0, //int cost;
+ 0 //int value;
+ },
+};
+
+int bg_numPclasses = sizeof( bg_classList ) / sizeof( bg_classList[ 0 ] );
+
+//separate from bg_classList to work around char struct init bug
+classAttributeOverrides_t bg_classOverrideList[ PCL_NUM_CLASSES ];
+
+/*
+==============
+BG_FindClassNumForName
+==============
+*/
+int BG_FindClassNumForName( char *name )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( !Q_stricmp( bg_classList[ i ].className, name ) )
+ return bg_classList[ i ].classNum;
+ }
+
+ //wimp out
+ return PCL_NONE;
+}
+
+/*
+==============
+BG_FindNameForClassNum
+==============
+*/
+char *BG_FindNameForClassNum( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ return bg_classList[ i ].className;
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindNameForClassNum\n" );
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindHumanNameForClassNum
+==============
+*/
+char *BG_FindHumanNameForClassNum( int pclass )
+{
+ int i;
+
+ if( bg_classOverrideList[ pclass ].humanName[ 0 ] != 0 )
+ return bg_classOverrideList[ pclass ].humanName;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ return bg_classList[ i ].humanName;
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHumanNameForClassNum\n" );
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindModelNameForClass
+==============
+*/
+char *BG_FindModelNameForClass( int pclass )
+{
+ int i;
+
+ if( bg_classOverrideList[ pclass ].modelName[ 0 ] != 0 )
+ return bg_classOverrideList[ pclass ].modelName;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ return bg_classList[ i ].modelName;
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelNameForClass\n" );
+ //note: must return a valid modelName!
+ return bg_classList[ 0 ].modelName;
+}
+
+/*
+==============
+BG_FindModelScaleForClass
+==============
+*/
+float BG_FindModelScaleForClass( int pclass )
+{
+ int i;
+
+ if( bg_classOverrideList[ pclass ].modelScale != 0.0f )
+ return bg_classOverrideList[ pclass ].modelScale;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].modelScale;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelScaleForClass( %d )\n", pclass );
+ return 1.0f;
+}
+
+/*
+==============
+BG_FindSkinNameForClass
+==============
+*/
+char *BG_FindSkinNameForClass( int pclass )
+{
+ int i;
+
+ if( bg_classOverrideList[ pclass ].skinName[ 0 ] != 0 )
+ return bg_classOverrideList[ pclass ].skinName;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ return bg_classList[ i ].skinName;
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSkinNameForClass\n" );
+ //note: must return a valid modelName!
+ return bg_classList[ 0 ].skinName;
+}
+
+/*
+==============
+BG_FindShadowScaleForClass
+==============
+*/
+float BG_FindShadowScaleForClass( int pclass )
+{
+ int i;
+
+ if( bg_classOverrideList[ pclass ].shadowScale != 0.0f )
+ return bg_classOverrideList[ pclass ].shadowScale;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].shadowScale;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindShadowScaleForClass( %d )\n", pclass );
+ return 1.0f;
+}
+
+/*
+==============
+BG_FindHudNameForClass
+==============
+*/
+char *BG_FindHudNameForClass( int pclass )
+{
+ int i;
+
+ if( bg_classOverrideList[ pclass ].hudName[ 0 ] != 0 )
+ return bg_classOverrideList[ pclass ].hudName;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ return bg_classList[ i ].hudName;
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHudNameForClass\n" );
+ //note: must return a valid hudName!
+ return bg_classList[ 0 ].hudName;
+}
+
+/*
+==============
+BG_FindStagesForClass
+==============
+*/
+qboolean BG_FindStagesForClass( int pclass, stage_t stage )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ if( bg_classList[ i ].stages & ( 1 << stage ) )
+ return qtrue;
+ else
+ return qfalse;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStagesForClass\n" );
+ return qfalse;
+}
+
+/*
+==============
+BG_FindBBoxForClass
+==============
+*/
+void BG_FindBBoxForClass( int pclass, vec3_t mins, vec3_t maxs, vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ if( mins != NULL )
+ {
+ VectorCopy( bg_classList[ i ].mins, mins );
+
+ if( VectorLength( bg_classOverrideList[ pclass ].mins ) )
+ VectorCopy( bg_classOverrideList[ pclass ].mins, mins );
+ }
+
+ if( maxs != NULL )
+ {
+ VectorCopy( bg_classList[ i ].maxs, maxs );
+
+ if( VectorLength( bg_classOverrideList[ pclass ].maxs ) )
+ VectorCopy( bg_classOverrideList[ pclass ].maxs, maxs );
+ }
+
+ if( cmaxs != NULL )
+ {
+ VectorCopy( bg_classList[ i ].crouchMaxs, cmaxs );
+
+ if( VectorLength( bg_classOverrideList[ pclass ].crouchMaxs ) )
+ VectorCopy( bg_classOverrideList[ pclass ].crouchMaxs, cmaxs );
+ }
+
+ if( dmins != NULL )
+ {
+ VectorCopy( bg_classList[ i ].deadMins, dmins );
+
+ if( VectorLength( bg_classOverrideList[ pclass ].deadMins ) )
+ VectorCopy( bg_classOverrideList[ pclass ].deadMins, dmins );
+ }
+
+ if( dmaxs != NULL )
+ {
+ VectorCopy( bg_classList[ i ].deadMaxs, dmaxs );
+
+ if( VectorLength( bg_classOverrideList[ pclass ].deadMaxs ) )
+ VectorCopy( bg_classOverrideList[ pclass ].deadMaxs, dmaxs );
+ }
+
+ return;
+ }
+ }
+
+ if( mins != NULL )
+ VectorCopy( bg_classList[ 0 ].mins, mins );
+
+ if( maxs != NULL )
+ VectorCopy( bg_classList[ 0 ].maxs, maxs );
+
+ if( cmaxs != NULL )
+ VectorCopy( bg_classList[ 0 ].crouchMaxs, cmaxs );
+
+ if( dmins != NULL )
+ VectorCopy( bg_classList[ 0 ].deadMins, dmins );
+
+ if( dmaxs != NULL )
+ VectorCopy( bg_classList[ 0 ].deadMaxs, dmaxs );
+}
+
+/*
+==============
+BG_FindZOffsetForClass
+==============
+*/
+float BG_FindZOffsetForClass( int pclass )
+{
+ int i;
+
+ if( bg_classOverrideList[ pclass ].zOffset != 0.0f )
+ return bg_classOverrideList[ pclass ].zOffset;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].zOffset;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindZOffsetForClass\n" );
+ return 0.0f;
+}
+
+/*
+==============
+BG_FindViewheightForClass
+==============
+*/
+void BG_FindViewheightForClass( int pclass, int *viewheight, int *cViewheight )
+{
+ int i;
+ int vh = 0;
+ int cvh = 0;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ vh = bg_classList[ i ].viewheight;
+ cvh = bg_classList[ i ].crouchViewheight;
+ break;
+ }
+ }
+
+ if( bg_classOverrideList[ pclass ].viewheight != 0 )
+ vh = bg_classOverrideList[ pclass ].viewheight;
+ if( bg_classOverrideList[ pclass ].crouchViewheight != 0 )
+ cvh = bg_classOverrideList[ pclass ].crouchViewheight;
+
+
+ if( vh == 0 )
+ vh = bg_classList[ 0 ].viewheight;
+ if( cvh == 0 )
+ cvh = bg_classList[ 0 ].crouchViewheight;
+
+ if( viewheight != NULL )
+ *viewheight = vh;
+ if( cViewheight != NULL )
+ *cViewheight = cvh;
+}
+
+/*
+==============
+BG_FindHealthForClass
+==============
+*/
+int BG_FindHealthForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].health;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHealthForClass\n" );
+ return 100;
+}
+
+/*
+==============
+BG_FindFallDamageForClass
+==============
+*/
+float BG_FindFallDamageForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].fallDamage;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFallDamageForClass\n" );
+ return 100;
+}
+
+/*
+==============
+BG_FindRegenRateForClass
+==============
+*/
+int BG_FindRegenRateForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].regenRate;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindRegenRateForClass\n" );
+ return 0;
+}
+
+/*
+==============
+BG_FindFovForClass
+==============
+*/
+int BG_FindFovForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].fov;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFovForClass\n" );
+ return 90;
+}
+
+/*
+==============
+BG_FindBobForClass
+==============
+*/
+float BG_FindBobForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].bob;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBobForClass\n" );
+ return 0.002;
+}
+
+/*
+==============
+BG_FindBobCycleForClass
+==============
+*/
+float BG_FindBobCycleForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].bobCycle;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBobCycleForClass\n" );
+ return 1.0f;
+}
+
+/*
+==============
+BG_FindSpeedForClass
+==============
+*/
+float BG_FindSpeedForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].speed;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSpeedForClass\n" );
+ return 1.0f;
+}
+
+/*
+==============
+BG_FindAccelerationForClass
+==============
+*/
+float BG_FindAccelerationForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].acceleration;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindAccelerationForClass\n" );
+ return 10.0f;
+}
+
+/*
+==============
+BG_FindAirAccelerationForClass
+==============
+*/
+float BG_FindAirAccelerationForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].airAcceleration;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindAirAccelerationForClass\n" );
+ return 1.0f;
+}
+
+/*
+==============
+BG_FindFrictionForClass
+==============
+*/
+float BG_FindFrictionForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].friction;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFrictionForClass\n" );
+ return 6.0f;
+}
+
+/*
+==============
+BG_FindStopSpeedForClass
+==============
+*/
+float BG_FindStopSpeedForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].stopSpeed;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStopSpeedForClass\n" );
+ return 100.0f;
+}
+
+/*
+==============
+BG_FindJumpMagnitudeForClass
+==============
+*/
+float BG_FindJumpMagnitudeForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].jumpMagnitude;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindJumpMagnitudeForClass\n" );
+ return 270.0f;
+}
+
+/*
+==============
+BG_FindKnockbackScaleForClass
+==============
+*/
+float BG_FindKnockbackScaleForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].knockbackScale;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindKnockbackScaleForClass\n" );
+ return 1.0f;
+}
+
+/*
+==============
+BG_FindSteptimeForClass
+==============
+*/
+int BG_FindSteptimeForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].steptime;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSteptimeForClass\n" );
+ return 200;
+}
+
+/*
+==============
+BG_ClassHasAbility
+==============
+*/
+qboolean BG_ClassHasAbility( int pclass, int ability )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return ( bg_classList[ i ].abilities & ability );
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindStartWeaponForClass
+==============
+*/
+weapon_t BG_FindStartWeaponForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].startWeapon;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStartWeaponForClass\n" );
+ return WP_NONE;
+}
+
+/*
+==============
+BG_FindBuildDistForClass
+==============
+*/
+float BG_FindBuildDistForClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].buildDist;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBuildDistForClass\n" );
+ return 0.0f;
+}
+
+/*
+==============
+BG_ClassCanEvolveFromTo
+==============
+*/
+int BG_ClassCanEvolveFromTo( int fclass, int tclass, int credits, int num )
+{
+ int i, j, cost;
+
+ cost = BG_FindCostOfClass( tclass );
+
+ //base case
+ if( credits < cost )
+ return -1;
+
+ if( fclass == PCL_NONE || tclass == PCL_NONE )
+ return -1;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == fclass )
+ {
+ for( j = 0; j < 3; j++ )
+ if( bg_classList[ i ].children[ j ] == tclass )
+ return num + cost;
+
+ for( j = 0; j < 3; j++ )
+ {
+ int sub;
+
+ cost = BG_FindCostOfClass( bg_classList[ i ].children[ j ] );
+ sub = BG_ClassCanEvolveFromTo( bg_classList[ i ].children[ j ],
+ tclass, credits - cost, num + cost );
+ if( sub >= 0 )
+ return sub;
+ }
+
+ return -1; //may as well return by this point
+ }
+ }
+
+ return -1;
+}
+
+/*
+==============
+BG_FindValueOfClass
+==============
+*/
+int BG_FindValueOfClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].value;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindValueOfClass\n" );
+ return 0;
+}
+
+/*
+==============
+BG_FindCostOfClass
+==============
+*/
+int BG_FindCostOfClass( int pclass )
+{
+ int i;
+
+ for( i = 0; i < bg_numPclasses; i++ )
+ {
+ if( bg_classList[ i ].classNum == pclass )
+ {
+ return bg_classList[ i ].cost;
+ }
+ }
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindCostOfClass\n" );
+ return 0;
+}
+
+/*
+==============
+BG_FindOverrideForClass
+==============
+*/
+static classAttributeOverrides_t *BG_FindOverrideForClass( int pclass )
+{
+ return &bg_classOverrideList[ pclass ];
+}
+
+/*
+======================
+BG_ParseClassFile
+
+Parses a configuration file describing a class
+======================
+*/
+static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides_t *cao )
+{
+ char *text_p;
+ int i;
+ int len;
+ char *token;
+ char text[ 20000 ];
+ fileHandle_t f;
+ float scale = 0.0f;
+
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len < 0 )
+ return qfalse;
+
+ if( len == 0 || len >= sizeof( text ) - 1 )
+ {
+ trap_FS_FCloseFile( f );
+ Com_Printf( S_COLOR_RED "ERROR: Class file %s is %s\n", filename,
+ len == 0 ? "empty" : "too long" );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "model" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( cao->modelName, token, sizeof( cao->modelName ) );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "skin" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( cao->skinName, token, sizeof( cao->skinName ) );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "hud" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( cao->hudName, token, sizeof( cao->hudName ) );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "modelScale" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ scale = atof( token );
+
+ if( scale < 0.0f )
+ scale = 0.0f;
+
+ cao->modelScale = scale;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "shadowScale" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ scale = atof( token );
+
+ if( scale < 0.0f )
+ scale = 0.0f;
+
+ cao->shadowScale = scale;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "mins" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ cao->mins[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "maxs" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ cao->maxs[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "deadMins" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ cao->deadMins[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "deadMaxs" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ cao->deadMaxs[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "crouchMaxs" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ cao->crouchMaxs[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "viewheight" ) )
+ {
+ token = COM_Parse( &text_p );
+ cao->viewheight = atoi( token );
+ continue;
+ }
+ else if( !Q_stricmp( token, "crouchViewheight" ) )
+ {
+ token = COM_Parse( &text_p );
+ cao->crouchViewheight = atoi( token );
+ continue;
+ }
+ else if( !Q_stricmp( token, "zOffset" ) )
+ {
+ float offset;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ offset = atof( token );
+
+ cao->zOffset = offset;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "name" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( cao->humanName, token, sizeof( cao->humanName ) );
+
+ continue;
+ }
+
+
+ Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+BG_InitClassOverrides
+
+Set any overrides specfied by file
+===============
+*/
+void BG_InitClassOverrides( void )
+{
+ int i;
+ classAttributeOverrides_t *cao;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ cao = BG_FindOverrideForClass( i );
+
+ BG_ParseClassFile( va( "overrides/classes/%s.cfg", BG_FindNameForClassNum( i ) ), cao );
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+weaponAttributes_t bg_weapons[ ] =
+{
+ {
+ WP_BLASTER, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ 0, //int slots;
+ "blaster", //char *weaponName;
+ "Blaster", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ BLASTER_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ BLASTER_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_MACHINEGUN, //int weaponNum;
+ RIFLE_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "rifle", //char *weaponName;
+ "Rifle", //char *weaponHumanName;
+ RIFLE_CLIPSIZE, //int maxAmmo;
+ RIFLE_MAXCLIPS, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ RIFLE_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ RIFLE_RELOAD, //int reloadTime;
+ RIFLE_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_SHOTGUN, //int weaponNum;
+ SHOTGUN_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "shotgun", //char *weaponName;
+ "Shotgun", //char *weaponHumanName;
+ SHOTGUN_SHELLS, //int maxAmmo;
+ SHOTGUN_MAXCLIPS, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ SHOTGUN_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ SHOTGUN_RELOAD, //int reloadTime;
+ SHOTGUN_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_FLAMER, //int weaponNum;
+ FLAMER_PRICE, //int price;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "flamer", //char *weaponName;
+ "Flame Thrower", //char *weaponHumanName;
+ FLAMER_GAS, //int maxAmmo;
+ 0, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ FLAMER_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ FLAMER_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_CHAINGUN, //int weaponNum;
+ CHAINGUN_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "chaingun", //char *weaponName;
+ "Chaingun", //char *weaponHumanName;
+ CHAINGUN_BULLETS, //int maxAmmo;
+ 0, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ CHAINGUN_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ CHAINGUN_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_MASS_DRIVER, //int weaponNum;
+ MDRIVER_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "mdriver", //char *weaponName;
+ "Mass Driver", //char *weaponHumanName;
+ MDRIVER_CLIPSIZE, //int maxAmmo;
+ MDRIVER_MAXCLIPS, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qtrue, //int usesEnergy;
+ MDRIVER_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ MDRIVER_RELOAD, //int reloadTime;
+ MDRIVER_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qtrue, //qboolean canZoom;
+ 20.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_PULSE_RIFLE, //int weaponNum;
+ PRIFLE_PRICE, //int price;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "prifle", //char *weaponName;
+ "Pulse Rifle", //char *weaponHumanName;
+ PRIFLE_CLIPS, //int maxAmmo;
+ PRIFLE_MAXCLIPS, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qtrue, //int usesEnergy;
+ PRIFLE_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ PRIFLE_RELOAD, //int reloadTime;
+ PRIFLE_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_LUCIFER_CANNON, //int weaponNum;
+ LCANNON_PRICE, //int price;
+ ( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "lcannon", //char *weaponName;
+ "Lucifer Cannon", //char *weaponHumanName;
+ LCANNON_AMMO, //int maxAmmo;
+ 0, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qtrue, //int usesEnergy;
+ LCANNON_REPEAT, //int repeatRate1;
+ LCANNON_CHARGEREPEAT, //int repeatRate2;
+ 0, //int repeatRate3;
+ LCANNON_RELOAD, //int reloadTime;
+ LCANNON_K_SCALE, //float knockbackScale;
+ qtrue, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_LAS_GUN, //int weaponNum;
+ LASGUN_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "lgun", //char *weaponName;
+ "Las Gun", //char *weaponHumanName;
+ LASGUN_AMMO, //int maxAmmo;
+ 0, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qtrue, //int usesEnergy;
+ LASGUN_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ LASGUN_RELOAD, //int reloadTime;
+ LASGUN_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_PAIN_SAW, //int weaponNum;
+ PAINSAW_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "psaw", //char *weaponName;
+ "Pain Saw", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ PAINSAW_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ PAINSAW_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_GRENADE, //int weaponNum;
+ GRENADE_PRICE, //int price;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_NONE, //int slots;
+ "grenade", //char *weaponName;
+ "Grenade", //char *weaponHumanName;
+ 1, //int maxAmmo;
+ 0, //int maxClips;
+ qfalse, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ GRENADE_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ GRENADE_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_HBUILD, //int weaponNum;
+ HBUILD_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "ckit", //char *weaponName;
+ "Construction Kit", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ HBUILD_REPEAT, //int repeatRate1;
+ HBUILD_REPEAT, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ 0.0f, //float knockbackScale;
+ qtrue, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ HBUILD_DELAY, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_HBUILD2, //int weaponNum;
+ HBUILD2_PRICE, //int price;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "ackit", //char *weaponName;
+ "Adv Construction Kit",//char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ HBUILD2_REPEAT, //int repeatRate1;
+ HBUILD2_REPEAT, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ 0.0f, //float knockbackScale;
+ qtrue, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ HBUILD2_DELAY, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_ABUILD, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "abuild", //char *weaponName;
+ "Alien build weapon", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ ABUILDER_BUILD_REPEAT,//int repeatRate1;
+ ABUILDER_BUILD_REPEAT,//int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ 0.0f, //float knockbackScale;
+ qtrue, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ ABUILDER_BASE_DELAY, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ABUILD2, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "abuildupg", //char *weaponName;
+ "Alien build weapon2",//char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ ABUILDER_BUILD_REPEAT,//int repeatRate1;
+ ABUILDER_CLAW_REPEAT, //int repeatRate2;
+ ABUILDER_BLOB_REPEAT, //int repeatRate3;
+ 0, //int reloadTime;
+ ABUILDER_CLAW_K_SCALE,//float knockbackScale;
+ qtrue, //qboolean hasAltMode;
+ qtrue, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qtrue, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ ABUILDER_ADV_DELAY, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL0, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level0", //char *weaponName;
+ "Bite", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL0_BITE_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL0_BITE_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL1, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level1", //char *weaponName;
+ "Claws", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL1_CLAW_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL1_CLAW_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL1_UPG, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level1upg", //char *weaponName;
+ "Claws Upgrade", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL1_CLAW_U_REPEAT, //int repeatRate1;
+ LEVEL1_PCLOUD_REPEAT, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL1_CLAW_U_K_SCALE,//float knockbackScale;
+ qtrue, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL2, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level2", //char *weaponName;
+ "Bite", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL2_CLAW_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL2_CLAW_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL2_UPG, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level2upg", //char *weaponName;
+ "Zap", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL2_CLAW_U_REPEAT, //int repeatRate1;
+ LEVEL2_AREAZAP_REPEAT,//int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL2_CLAW_U_K_SCALE,//float knockbackScale;
+ qtrue, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL3, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level3", //char *weaponName;
+ "Pounce", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL3_CLAW_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL3_CLAW_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL3_UPG, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level3upg", //char *weaponName;
+ "Pounce (upgrade)", //char *weaponHumanName;
+ 3, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL3_CLAW_U_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ LEVEL3_BOUNCEBALL_REPEAT,//int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL3_CLAW_U_K_SCALE,//float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qtrue, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qtrue, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_ALEVEL4, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "level4", //char *weaponName;
+ "Charge", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ LEVEL4_CLAW_REPEAT, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ LEVEL4_CLAW_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_LOCKBLOB_LAUNCHER, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "lockblob", //char *weaponName;
+ "Lock Blob", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ 500, //int repeatRate1;
+ 500, //int repeatRate2;
+ 500, //int repeatRate3;
+ 0, //int reloadTime;
+ LOCKBLOB_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_HIVE, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "hive", //char *weaponName;
+ "Hive", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ 500, //int repeatRate1;
+ 500, //int repeatRate2;
+ 500, //int repeatRate3;
+ 0, //int reloadTime;
+ HIVE_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_ALIENS //WUTeam_t team;
+ },
+ {
+ WP_MGTURRET, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "mgturret", //char *weaponName;
+ "Machinegun Turret", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qfalse, //int usesEnergy;
+ 0, //int repeatRate1;
+ 0, //int repeatRate2;
+ 0, //int repeatRate3;
+ 0, //int reloadTime;
+ MGTURRET_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ WP_TESLAGEN, //int weaponNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_WEAPON, //int slots;
+ "teslagen", //char *weaponName;
+ "Tesla Generator", //char *weaponHumanName;
+ 0, //int maxAmmo;
+ 0, //int maxClips;
+ qtrue, //int infiniteAmmo;
+ qtrue, //int usesEnergy;
+ 500, //int repeatRate1;
+ 500, //int repeatRate2;
+ 500, //int repeatRate3;
+ 0, //int reloadTime;
+ TESLAGEN_K_SCALE, //float knockbackScale;
+ qfalse, //qboolean hasAltMode;
+ qfalse, //qboolean hasThirdMode;
+ qfalse, //qboolean canZoom;
+ 90.0f, //float zoomFov;
+ qfalse, //qboolean purchasable;
+ qfalse, //qboolean longRanged;
+ 0, //int buildDelay;
+ WUT_HUMANS //WUTeam_t team;
+ }
+};
+
+int bg_numWeapons = sizeof( bg_weapons ) / sizeof( bg_weapons[ 0 ] );
+
+/*
+==============
+BG_FindPriceForWeapon
+==============
+*/
+int BG_FindPriceForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].price;
+ }
+ }
+
+ return 100;
+}
+
+/*
+==============
+BG_FindStagesForWeapon
+==============
+*/
+qboolean BG_FindStagesForWeapon( int weapon, stage_t stage )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ if( bg_weapons[ i ].stages & ( 1 << stage ) )
+ return qtrue;
+ else
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindSlotsForWeapon
+==============
+*/
+int BG_FindSlotsForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].slots;
+ }
+ }
+
+ return SLOT_WEAPON;
+}
+
+/*
+==============
+BG_FindNameForWeapon
+==============
+*/
+char *BG_FindNameForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ return bg_weapons[ i ].weaponName;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindWeaponNumForName
+==============
+*/
+int BG_FindWeaponNumForName( char *name )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( !Q_stricmp( bg_weapons[ i ].weaponName, name ) )
+ return bg_weapons[ i ].weaponNum;
+ }
+
+ //wimp out
+ return WP_NONE;
+}
+
+/*
+==============
+BG_FindHumanNameForWeapon
+==============
+*/
+char *BG_FindHumanNameForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ return bg_weapons[ i ].weaponHumanName;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindAmmoForWeapon
+==============
+*/
+void BG_FindAmmoForWeapon( int weapon, int *maxAmmo, int *maxClips )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ if( maxAmmo != NULL )
+ *maxAmmo = bg_weapons[ i ].maxAmmo;
+ if( maxClips != NULL )
+ *maxClips = bg_weapons[ i ].maxClips;
+
+ //no need to keep going
+ break;
+ }
+ }
+}
+
+/*
+==============
+BG_FindInfinteAmmoForWeapon
+==============
+*/
+qboolean BG_FindInfinteAmmoForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].infiniteAmmo;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindUsesEnergyForWeapon
+==============
+*/
+qboolean BG_FindUsesEnergyForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].usesEnergy;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindRepeatRate1ForWeapon
+==============
+*/
+int BG_FindRepeatRate1ForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ return bg_weapons[ i ].repeatRate1;
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindRepeatRate2ForWeapon
+==============
+*/
+int BG_FindRepeatRate2ForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ return bg_weapons[ i ].repeatRate2;
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindRepeatRate3ForWeapon
+==============
+*/
+int BG_FindRepeatRate3ForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ return bg_weapons[ i ].repeatRate3;
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindReloadTimeForWeapon
+==============
+*/
+int BG_FindReloadTimeForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].reloadTime;
+ }
+ }
+
+ return 1000;
+}
+
+/*
+==============
+BG_FindKnockbackScaleForWeapon
+==============
+*/
+float BG_FindKnockbackScaleForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].knockbackScale;
+ }
+ }
+
+ return 1.0f;
+}
+
+/*
+==============
+BG_WeaponHasAltMode
+==============
+*/
+qboolean BG_WeaponHasAltMode( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].hasAltMode;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_WeaponHasThirdMode
+==============
+*/
+qboolean BG_WeaponHasThirdMode( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].hasThirdMode;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_WeaponCanZoom
+==============
+*/
+qboolean BG_WeaponCanZoom( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].canZoom;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindZoomFovForWeapon
+==============
+*/
+float BG_FindZoomFovForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].zoomFov;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindPurchasableForWeapon
+==============
+*/
+qboolean BG_FindPurchasableForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].purchasable;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindLongRangeForWeapon
+==============
+*/
+qboolean BG_FindLongRangedForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].longRanged;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindBuildDelayForWeapon
+==============
+*/
+int BG_FindBuildDelayForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].buildDelay;
+ }
+ }
+
+ return 0;
+}
+
+/*
+==============
+BG_FindTeamForWeapon
+==============
+*/
+WUTeam_t BG_FindTeamForWeapon( int weapon )
+{
+ int i;
+
+ for( i = 0; i < bg_numWeapons; i++ )
+ {
+ if( bg_weapons[ i ].weaponNum == weapon )
+ {
+ return bg_weapons[ i ].team;
+ }
+ }
+
+ return WUT_NONE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+upgradeAttributes_t bg_upgrades[ ] =
+{
+ {
+ UP_LIGHTARMOUR, //int upgradeNum;
+ LIGHTARMOUR_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_TORSO|SLOT_ARMS|SLOT_LEGS, //int slots;
+ "larmour", //char *upgradeName;
+ "Light Armour", //char *upgradeHumanName;
+ "icons/iconu_larmour",
+ qtrue, //qboolean purchasable
+ qfalse, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ UP_HELMET, //int upgradeNum;
+ HELMET_PRICE, //int price;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_HEAD, //int slots;
+ "helmet", //char *upgradeName;
+ "Helmet", //char *upgradeHumanName;
+ "icons/iconu_helmet",
+ qtrue, //qboolean purchasable
+ qfalse, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ UP_MEDKIT, //int upgradeNum;
+ MEDKIT_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_NONE, //int slots;
+ "medkit", //char *upgradeName;
+ "Medkit", //char *upgradeHumanName;
+ "icons/iconu_atoxin",
+ qfalse, //qboolean purchasable
+ qtrue, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ UP_BATTPACK, //int upgradeNum;
+ BATTPACK_PRICE, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_BACKPACK, //int slots;
+ "battpack", //char *upgradeName;
+ "Battery Pack", //char *upgradeHumanName;
+ "icons/iconu_battpack",
+ qtrue, //qboolean purchasable
+ qfalse, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ UP_JETPACK, //int upgradeNum;
+ JETPACK_PRICE, //int price;
+ ( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_BACKPACK, //int slots;
+ "jetpack", //char *upgradeName;
+ "Jet Pack", //char *upgradeHumanName;
+ "icons/iconu_jetpack",
+ qtrue, //qboolean purchasable
+ qtrue, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ UP_BATTLESUIT, //int upgradeNum;
+ BSUIT_PRICE, //int price;
+ ( 1 << S3 ), //int stages
+ SLOT_HEAD|SLOT_TORSO|SLOT_ARMS|SLOT_LEGS|SLOT_BACKPACK, //int slots;
+ "bsuit", //char *upgradeName;
+ "Battlesuit", //char *upgradeHumanName;
+ "icons/iconu_bsuit",
+ qtrue, //qboolean purchasable
+ qfalse, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ UP_GRENADE, //int upgradeNum;
+ GRENADE_PRICE, //int price;
+ ( 1 << S2 )|( 1 << S3 ),//int stages
+ SLOT_NONE, //int slots;
+ "gren", //char *upgradeName;
+ "Grenade", //char *upgradeHumanName;
+ 0,
+ qtrue, //qboolean purchasable
+ qtrue, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ },
+ {
+ UP_AMMO, //int upgradeNum;
+ 0, //int price;
+ ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages
+ SLOT_NONE, //int slots;
+ "ammo", //char *upgradeName;
+ "Ammunition", //char *upgradeHumanName;
+ 0,
+ qtrue, //qboolean purchasable
+ qfalse, //qboolean usable
+ WUT_HUMANS //WUTeam_t team;
+ }
+};
+
+int bg_numUpgrades = sizeof( bg_upgrades ) / sizeof( bg_upgrades[ 0 ] );
+
+/*
+==============
+BG_FindPriceForUpgrade
+==============
+*/
+int BG_FindPriceForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ {
+ return bg_upgrades[ i ].price;
+ }
+ }
+
+ return 100;
+}
+
+/*
+==============
+BG_FindStagesForUpgrade
+==============
+*/
+qboolean BG_FindStagesForUpgrade( int upgrade, stage_t stage )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ {
+ if( bg_upgrades[ i ].stages & ( 1 << stage ) )
+ return qtrue;
+ else
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindSlotsForUpgrade
+==============
+*/
+int BG_FindSlotsForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ {
+ return bg_upgrades[ i ].slots;
+ }
+ }
+
+ return SLOT_NONE;
+}
+
+/*
+==============
+BG_FindNameForUpgrade
+==============
+*/
+char *BG_FindNameForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ return bg_upgrades[ i ].upgradeName;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindUpgradeNumForName
+==============
+*/
+int BG_FindUpgradeNumForName( char *name )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( !Q_stricmp( bg_upgrades[ i ].upgradeName, name ) )
+ return bg_upgrades[ i ].upgradeNum;
+ }
+
+ //wimp out
+ return UP_NONE;
+}
+
+/*
+==============
+BG_FindHumanNameForUpgrade
+==============
+*/
+char *BG_FindHumanNameForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ return bg_upgrades[ i ].upgradeHumanName;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindIconForUpgrade
+==============
+*/
+char *BG_FindIconForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ return bg_upgrades[ i ].icon;
+ }
+
+ //wimp out
+ return 0;
+}
+
+/*
+==============
+BG_FindPurchasableForUpgrade
+==============
+*/
+qboolean BG_FindPurchasableForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ return bg_upgrades[ i ].purchasable;
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindUsableForUpgrade
+==============
+*/
+qboolean BG_FindUsableForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ return bg_upgrades[ i ].usable;
+ }
+
+ return qfalse;
+}
+
+/*
+==============
+BG_FindTeamForUpgrade
+==============
+*/
+WUTeam_t BG_FindTeamForUpgrade( int upgrade )
+{
+ int i;
+
+ for( i = 0; i < bg_numUpgrades; i++ )
+ {
+ if( bg_upgrades[ i ].upgradeNum == upgrade )
+ {
+ return bg_upgrades[ i ].team;
+ }
+ }
+
+ return WUT_NONE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+================
+BG_EvaluateTrajectory
+
+================
+*/
+void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result )
+{
+ float deltaTime;
+ float phase;
+
+ switch( tr->trType )
+ {
+ case TR_STATIONARY:
+ case TR_INTERPOLATE:
+ VectorCopy( tr->trBase, result );
+ break;
+
+ case TR_LINEAR:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorMA( tr->trBase, deltaTime, tr->trDelta, result );
+ break;
+
+ case TR_SINE:
+ deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration;
+ phase = sin( deltaTime * M_PI * 2 );
+ VectorMA( tr->trBase, phase, tr->trDelta, result );
+ break;
+
+ case TR_LINEAR_STOP:
+ if( atTime > tr->trTime + tr->trDuration )
+ atTime = tr->trTime + tr->trDuration;
+
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ if( deltaTime < 0 )
+ deltaTime = 0;
+
+ VectorMA( tr->trBase, deltaTime, tr->trDelta, result );
+ break;
+
+ case TR_GRAVITY:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorMA( tr->trBase, deltaTime, tr->trDelta, result );
+ result[ 2 ] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity...
+ break;
+
+ case TR_BUOYANCY:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorMA( tr->trBase, deltaTime, tr->trDelta, result );
+ result[ 2 ] += 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity...
+ break;
+
+ default:
+ Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime );
+ break;
+ }
+}
+
+/*
+================
+BG_EvaluateTrajectoryDelta
+
+For determining velocity at a given time
+================
+*/
+void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result )
+{
+ float deltaTime;
+ float phase;
+
+ switch( tr->trType )
+ {
+ case TR_STATIONARY:
+ case TR_INTERPOLATE:
+ VectorClear( result );
+ break;
+
+ case TR_LINEAR:
+ VectorCopy( tr->trDelta, result );
+ break;
+
+ case TR_SINE:
+ deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration;
+ phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos
+ phase *= 0.5;
+ VectorScale( tr->trDelta, phase, result );
+ break;
+
+ case TR_LINEAR_STOP:
+ if( atTime > tr->trTime + tr->trDuration )
+ {
+ VectorClear( result );
+ return;
+ }
+ VectorCopy( tr->trDelta, result );
+ break;
+
+ case TR_GRAVITY:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorCopy( tr->trDelta, result );
+ result[ 2 ] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity...
+ break;
+
+ case TR_BUOYANCY:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorCopy( tr->trDelta, result );
+ result[ 2 ] += DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity...
+ break;
+
+ default:
+ Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime );
+ break;
+ }
+}
+
+char *eventnames[ ] =
+{
+ "EV_NONE",
+
+ "EV_FOOTSTEP",
+ "EV_FOOTSTEP_METAL",
+ "EV_FOOTSTEP_SQUELCH",
+ "EV_FOOTSPLASH",
+ "EV_FOOTWADE",
+ "EV_SWIM",
+
+ "EV_STEP_4",
+ "EV_STEP_8",
+ "EV_STEP_12",
+ "EV_STEP_16",
+
+ "EV_STEPDN_4",
+ "EV_STEPDN_8",
+ "EV_STEPDN_12",
+ "EV_STEPDN_16",
+
+ "EV_FALL_SHORT",
+ "EV_FALL_MEDIUM",
+ "EV_FALL_FAR",
+ "EV_FALLING",
+
+ "EV_JUMP",
+ "EV_WATER_TOUCH", // foot touches
+ "EV_WATER_LEAVE", // foot leaves
+ "EV_WATER_UNDER", // head touches
+ "EV_WATER_CLEAR", // head leaves
+
+ "EV_NOAMMO",
+ "EV_CHANGE_WEAPON",
+ "EV_FIRE_WEAPON",
+ "EV_FIRE_WEAPON2",
+ "EV_FIRE_WEAPON3",
+
+ "EV_PLAYER_RESPAWN", //TA: for fovwarp effects
+ "EV_PLAYER_TELEPORT_IN",
+ "EV_PLAYER_TELEPORT_OUT",
+
+ "EV_GRENADE_BOUNCE", // eventParm will be the soundindex
+
+ "EV_GENERAL_SOUND",
+ "EV_GLOBAL_SOUND", // no attenuation
+
+ "EV_BULLET_HIT_FLESH",
+ "EV_BULLET_HIT_WALL",
+
+ "EV_SHOTGUN",
+
+ "EV_MISSILE_HIT",
+ "EV_MISSILE_MISS",
+ "EV_MISSILE_MISS_METAL",
+ "EV_TESLATRAIL",
+ "EV_BULLET", // otherEntity is the shooter
+
+ "EV_LEV1_GRAB",
+ "EV_LEV4_CHARGE_PREPARE",
+ "EV_LEV4_CHARGE_START",
+
+ "EV_PAIN",
+ "EV_DEATH1",
+ "EV_DEATH2",
+ "EV_DEATH3",
+ "EV_OBITUARY",
+
+ "EV_GIB_PLAYER", // gib a previously living player
+
+ "EV_BUILD_CONSTRUCT", //TA
+ "EV_BUILD_DESTROY", //TA
+ "EV_BUILD_DELAY", //TA: can't build yet
+ "EV_BUILD_REPAIR", //TA: repairing buildable
+ "EV_BUILD_REPAIRED", //TA: buildable has full health
+ "EV_HUMAN_BUILDABLE_EXPLOSION",
+ "EV_ALIEN_BUILDABLE_EXPLOSION",
+ "EV_ALIEN_ACIDTUBE",
+
+ "EV_MEDKIT_USED",
+
+ "EV_ALIEN_EVOLVE",
+ "EV_ALIEN_EVOLVE_FAILED",
+
+ "EV_DEBUG_LINE",
+ "EV_STOPLOOPINGSOUND",
+ "EV_TAUNT",
+
+ "EV_OVERMIND_ATTACK", //TA: overmind under attack
+ "EV_OVERMIND_DYING", //TA: overmind close to death
+ "EV_OVERMIND_SPAWNS", //TA: overmind needs spawns
+
+ "EV_DCC_ATTACK", //TA: dcc under attack
+
+ "EV_RPTUSE_SOUND" //TA: trigger a sound
+};
+
+/*
+===============
+BG_AddPredictableEventToPlayerstate
+
+Handles the sequence numbers
+===============
+*/
+
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+
+void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps )
+{
+#ifdef _DEBUG
+ {
+ char buf[ 256 ];
+ trap_Cvar_VariableStringBuffer( "showevents", buf, sizeof( buf ) );
+
+ if( atof( buf ) != 0 )
+ {
+#ifdef QAGAME
+ Com_Printf( " game event svt %5d -> %5d: num = %20s parm %d\n",
+ ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[ newEvent ], eventParm);
+#else
+ Com_Printf( "Cgame event svt %5d -> %5d: num = %20s parm %d\n",
+ ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[ newEvent ], eventParm);
+#endif
+ }
+ }
+#endif
+ ps->events[ ps->eventSequence & ( MAX_PS_EVENTS - 1 ) ] = newEvent;
+ ps->eventParms[ ps->eventSequence & ( MAX_PS_EVENTS - 1 ) ] = eventParm;
+ ps->eventSequence++;
+}
+
+
+/*
+========================
+BG_PlayerStateToEntityState
+
+This is done after each set of usercmd_t on the server,
+and after local prediction on the client
+========================
+*/
+void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap )
+{
+ int i;
+
+ if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE )
+ s->eType = ET_INVISIBLE;
+ else if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ s->eType = ET_INVISIBLE;
+ else
+ s->eType = ET_PLAYER;
+
+ s->number = ps->clientNum;
+
+ s->pos.trType = TR_INTERPOLATE;
+ VectorCopy( ps->origin, s->pos.trBase );
+
+ if( snap )
+ SnapVector( s->pos.trBase );
+
+ //set the trDelta for flag direction
+ VectorCopy( ps->velocity, s->pos.trDelta );
+
+ s->apos.trType = TR_INTERPOLATE;
+ VectorCopy( ps->viewangles, s->apos.trBase );
+
+ if( snap )
+ SnapVector( s->apos.trBase );
+
+ //TA: i need for other things :)
+ //s->angles2[YAW] = ps->movementDir;
+ s->time2 = ps->movementDir;
+ s->legsAnim = ps->legsAnim;
+ s->torsoAnim = ps->torsoAnim;
+ s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number
+ // so corpses can also reference the proper config
+ s->eFlags = ps->eFlags;
+ if( ps->stats[STAT_HEALTH] <= 0 )
+ s->eFlags |= EF_DEAD;
+ else
+ s->eFlags &= ~EF_DEAD;
+
+ if( ps->stats[ STAT_STATE ] & SS_BLOBLOCKED )
+ s->eFlags |= EF_BLOBLOCKED;
+ else
+ s->eFlags &= ~EF_BLOBLOCKED;
+
+ if( ps->externalEvent )
+ {
+ s->event = ps->externalEvent;
+ s->eventParm = ps->externalEventParm;
+ }
+ else if( ps->entityEventSequence < ps->eventSequence )
+ {
+ int seq;
+
+ if( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS )
+ ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS;
+
+ seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 );
+ s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
+ s->eventParm = ps->eventParms[ seq ];
+ ps->entityEventSequence++;
+ }
+
+ s->weapon = ps->weapon;
+ s->groundEntityNum = ps->groundEntityNum;
+
+ //store items held and active items in modelindex and modelindex2
+ s->modelindex = 0;
+ s->modelindex2 = 0;
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, ps->stats ) )
+ {
+ s->modelindex |= 1 << i;
+
+ if( BG_UpgradeIsActive( i, ps->stats ) )
+ s->modelindex2 |= 1 << i;
+ }
+ }
+
+ // use misc field to store team/class info:
+ s->misc = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 );
+
+ //TA: have to get the surfNormal thru somehow...
+ VectorCopy( ps->grapplePoint, s->angles2 );
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ s->eFlags |= EF_WALLCLIMBCEILING;
+
+ s->loopSound = ps->loopSound;
+ s->generic1 = ps->generic1;
+
+ if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES )
+ s->generic1 = WPM_PRIMARY;
+
+ s->otherEntityNum = ps->otherEntityNum;
+}
+
+
+/*
+========================
+BG_PlayerStateToEntityStateExtraPolate
+
+This is done after each set of usercmd_t on the server,
+and after local prediction on the client
+========================
+*/
+void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap )
+{
+ int i;
+
+ if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE )
+ s->eType = ET_INVISIBLE;
+ else if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ s->eType = ET_INVISIBLE;
+ else
+ s->eType = ET_PLAYER;
+
+ s->number = ps->clientNum;
+
+ s->pos.trType = TR_LINEAR_STOP;
+ VectorCopy( ps->origin, s->pos.trBase );
+
+ if( snap )
+ SnapVector( s->pos.trBase );
+
+ // set the trDelta for flag direction and linear prediction
+ VectorCopy( ps->velocity, s->pos.trDelta );
+ // set the time for linear prediction
+ s->pos.trTime = time;
+ // set maximum extra polation time
+ s->pos.trDuration = 50; // 1000 / sv_fps (default = 20)
+
+ s->apos.trType = TR_INTERPOLATE;
+ VectorCopy( ps->viewangles, s->apos.trBase );
+ if( snap )
+ SnapVector( s->apos.trBase );
+
+ //TA: i need for other things :)
+ //s->angles2[YAW] = ps->movementDir;
+ s->time2 = ps->movementDir;
+ s->legsAnim = ps->legsAnim;
+ s->torsoAnim = ps->torsoAnim;
+ s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number
+ // so corpses can also reference the proper config
+ s->eFlags = ps->eFlags;
+
+ if( ps->stats[STAT_HEALTH] <= 0 )
+ s->eFlags |= EF_DEAD;
+ else
+ s->eFlags &= ~EF_DEAD;
+
+ if( ps->stats[ STAT_STATE ] & SS_BLOBLOCKED )
+ s->eFlags |= EF_BLOBLOCKED;
+ else
+ s->eFlags &= ~EF_BLOBLOCKED;
+
+ if( ps->externalEvent )
+ {
+ s->event = ps->externalEvent;
+ s->eventParm = ps->externalEventParm;
+ }
+ else if( ps->entityEventSequence < ps->eventSequence )
+ {
+ int seq;
+
+ if( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS )
+ ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS;
+
+ seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 );
+ s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
+ s->eventParm = ps->eventParms[ seq ];
+ ps->entityEventSequence++;
+ }
+
+ s->weapon = ps->weapon;
+ s->groundEntityNum = ps->groundEntityNum;
+
+ //store items held and active items in modelindex and modelindex2
+ s->modelindex = 0;
+ s->modelindex2 = 0;
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, ps->stats ) )
+ {
+ s->modelindex |= 1 << i;
+
+ if( BG_UpgradeIsActive( i, ps->stats ) )
+ s->modelindex2 |= 1 << i;
+ }
+ }
+
+ // use misc field to store team/class info:
+ s->misc = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 );
+
+ //TA: have to get the surfNormal thru somehow...
+ VectorCopy( ps->grapplePoint, s->angles2 );
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ s->eFlags |= EF_WALLCLIMBCEILING;
+
+ s->loopSound = ps->loopSound;
+ s->generic1 = ps->generic1;
+
+ if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES )
+ s->generic1 = WPM_PRIMARY;
+
+ s->otherEntityNum = ps->otherEntityNum;
+}
+
+/*
+========================
+BG_WeaponIsFull
+
+Check if a weapon has full ammo
+========================
+*/
+qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips )
+{
+ int maxAmmo, maxClips;
+
+ BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips );
+
+ if( BG_InventoryContainsUpgrade( UP_BATTPACK, stats ) )
+ maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
+
+ return ( maxAmmo == ammo ) && ( maxClips == clips );
+}
+
+/*
+========================
+BG_AddWeaponToInventory
+
+Give a player a weapon
+========================
+*/
+void BG_AddWeaponToInventory( int weapon, int stats[ ] )
+{
+ int weaponList;
+
+ weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 );
+
+ weaponList |= ( 1 << weapon );
+
+ stats[ STAT_WEAPONS ] = weaponList & 0x0000FFFF;
+ stats[ STAT_WEAPONS2 ] = ( weaponList & 0xFFFF0000 ) >> 16;
+
+ if( stats[ STAT_SLOTS ] & BG_FindSlotsForWeapon( weapon ) )
+ Com_Printf( S_COLOR_YELLOW "WARNING: Held items conflict with weapon %d\n", weapon );
+
+ stats[ STAT_SLOTS ] |= BG_FindSlotsForWeapon( weapon );
+}
+
+/*
+========================
+BG_RemoveWeaponToInventory
+
+Take a weapon from a player
+========================
+*/
+void BG_RemoveWeaponFromInventory( int weapon, int stats[ ] )
+{
+ int weaponList;
+
+ weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 );
+
+ weaponList &= ~( 1 << weapon );
+
+ stats[ STAT_WEAPONS ] = weaponList & 0x0000FFFF;
+ stats[ STAT_WEAPONS2 ] = ( weaponList & 0xFFFF0000 ) >> 16;
+
+ stats[ STAT_SLOTS ] &= ~BG_FindSlotsForWeapon( weapon );
+}
+
+/*
+========================
+BG_InventoryContainsWeapon
+
+Does the player hold a weapon?
+========================
+*/
+qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] )
+{
+ int weaponList;
+
+ weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 );
+
+ return( weaponList & ( 1 << weapon ) );
+}
+
+/*
+========================
+BG_AddUpgradeToInventory
+
+Give the player an upgrade
+========================
+*/
+void BG_AddUpgradeToInventory( int item, int stats[ ] )
+{
+ stats[ STAT_ITEMS ] |= ( 1 << item );
+
+ if( stats[ STAT_SLOTS ] & BG_FindSlotsForUpgrade( item ) )
+ Com_Printf( S_COLOR_YELLOW "WARNING: Held items conflict with upgrade %d\n", item );
+
+ stats[ STAT_SLOTS ] |= BG_FindSlotsForUpgrade( item );
+}
+
+/*
+========================
+BG_RemoveUpgradeFromInventory
+
+Take an upgrade from the player
+========================
+*/
+void BG_RemoveUpgradeFromInventory( int item, int stats[ ] )
+{
+ stats[ STAT_ITEMS ] &= ~( 1 << item );
+
+ stats[ STAT_SLOTS ] &= ~BG_FindSlotsForUpgrade( item );
+}
+
+/*
+========================
+BG_InventoryContainsUpgrade
+
+Does the player hold an upgrade?
+========================
+*/
+qboolean BG_InventoryContainsUpgrade( int item, int stats[ ] )
+{
+ return( stats[ STAT_ITEMS ] & ( 1 << item ) );
+}
+
+/*
+========================
+BG_ActivateUpgrade
+
+Activates an upgrade
+========================
+*/
+void BG_ActivateUpgrade( int item, int stats[ ] )
+{
+ stats[ STAT_ACTIVEITEMS ] |= ( 1 << item );
+}
+
+/*
+========================
+BG_DeactivateUpgrade
+
+Deactivates an upgrade
+========================
+*/
+void BG_DeactivateUpgrade( int item, int stats[ ] )
+{
+ stats[ STAT_ACTIVEITEMS ] &= ~( 1 << item );
+}
+
+/*
+========================
+BG_UpgradeIsActive
+
+Is this upgrade active?
+========================
+*/
+qboolean BG_UpgradeIsActive( int item, int stats[ ] )
+{
+ return( stats[ STAT_ACTIVEITEMS ] & ( 1 << item ) );
+}
+
+/*
+===============
+BG_RotateAxis
+
+Shared axis rotation function
+===============
+*/
+qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ],
+ vec3_t outAxis[ 3 ], qboolean inverse, qboolean ceiling )
+{
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+ vec3_t localNormal, xNormal;
+ float rotAngle;
+
+ //the grapplePoint being a surfNormal rotation Normal hack... see above :)
+ if( ceiling )
+ {
+ VectorCopy( ceilingNormal, localNormal );
+ VectorCopy( surfNormal, xNormal );
+ }
+ else
+ {
+ //cross the reference normal and the surface normal to get the rotation axis
+ VectorCopy( surfNormal, localNormal );
+ CrossProduct( localNormal, refNormal, xNormal );
+ VectorNormalize( xNormal );
+ }
+
+ //can't rotate with no rotation vector
+ if( VectorLength( xNormal ) != 0.0f )
+ {
+ rotAngle = RAD2DEG( acos( DotProduct( localNormal, refNormal ) ) );
+
+ if( inverse )
+ rotAngle = -rotAngle;
+
+ AngleNormalize180( rotAngle );
+
+ //hmmm could get away with only one rotation and some clever stuff later... but i'm lazy
+ RotatePointAroundVector( outAxis[ 0 ], xNormal, inAxis[ 0 ], -rotAngle );
+ RotatePointAroundVector( outAxis[ 1 ], xNormal, inAxis[ 1 ], -rotAngle );
+ RotatePointAroundVector( outAxis[ 2 ], xNormal, inAxis[ 2 ], -rotAngle );
+ }
+ else
+ return qfalse;
+
+ return qtrue;
+}
+
+/*
+===============
+BG_PositionBuildableRelativeToPlayer
+
+Find a place to build a buildable
+===============
+*/
+void BG_PositionBuildableRelativeToPlayer( const playerState_t *ps,
+ const vec3_t mins, const vec3_t maxs,
+ void (*trace)( trace_t *, const vec3_t, const vec3_t,
+ const vec3_t, const vec3_t, int, int ),
+ vec3_t outOrigin, vec3_t outAngles, trace_t *tr )
+{
+ vec3_t forward, entityOrigin, targetOrigin;
+ vec3_t angles, playerOrigin, playerNormal;
+ float buildDist;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( playerNormal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( ps->grapplePoint, playerNormal );
+ }
+ else
+ VectorSet( playerNormal, 0.0f, 0.0f, 1.0f );
+
+ VectorCopy( ps->viewangles, angles );
+ VectorCopy( ps->origin, playerOrigin );
+ buildDist = BG_FindBuildDistForClass( ps->stats[ STAT_PCLASS ] );
+
+ AngleVectors( angles, forward, NULL, NULL );
+ ProjectPointOnPlane( forward, forward, playerNormal );
+ VectorNormalize( forward );
+
+ VectorMA( playerOrigin, buildDist, forward, entityOrigin );
+
+ VectorCopy( entityOrigin, targetOrigin );
+
+ //so buildings can be placed facing slopes
+ VectorMA( entityOrigin, 32, playerNormal, entityOrigin );
+
+ //so buildings drop to floor
+ VectorMA( targetOrigin, -128, playerNormal, targetOrigin );
+
+ (*trace)( tr, entityOrigin, mins, maxs, targetOrigin, ps->clientNum, MASK_PLAYERSOLID );
+ VectorCopy( tr->endpos, entityOrigin );
+ VectorMA( entityOrigin, 0.1f, playerNormal, outOrigin );
+ vectoangles( forward, outAngles );
+}
+
+/*
+===============
+BG_GetValueOfEquipment
+
+Returns the equipment value of some human player's gear
+===============
+*/
+ int BG_GetValueOfEquipment( playerState_t *ps ) {
+ int i, worth = 0;
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, ps->stats ) )
+ worth += BG_FindPriceForUpgrade( i );
+ }
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_InventoryContainsWeapon( i, ps->stats ) )
+ worth += BG_FindPriceForWeapon( i );
+ }
+
+ return worth;
+ }
+/*
+===============
+BG_GetValueOfHuman
+
+Returns the kills value of some human player
+===============
+*/
+int BG_GetValueOfHuman( playerState_t *ps )
+{
+ float portion = BG_GetValueOfEquipment( ps ) / (float)HUMAN_MAXED;
+
+
+ if( portion < 0.01f )
+ portion = 0.01f;
+ else if( portion > 1.0f )
+ portion = 1.0f;
+
+ return ceil( ALIEN_MAX_SINGLE_KILLS * portion );
+}
+
+/*
+===============
+atof_neg
+
+atof with an allowance for negative values
+===============
+*/
+float atof_neg( char *token, qboolean allowNegative )
+{
+ float value;
+
+ value = atof( token );
+
+ if( !allowNegative && value < 0.0f )
+ value = 1.0f;
+
+ return value;
+}
+
+/*
+===============
+atoi_neg
+
+atoi with an allowance for negative values
+===============
+*/
+int atoi_neg( char *token, qboolean allowNegative )
+{
+ int value;
+
+ value = atoi( token );
+
+ if( !allowNegative && value < 0 )
+ value = 1;
+
+ return value;
+}
+
+/*
+===============
+BG_ParseCSVEquipmentList
+===============
+*/
+void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weaponsSize,
+ upgrade_t *upgrades, int upgradesSize )
+{
+ char buffer[ MAX_STRING_CHARS ];
+ int i = 0, j = 0;
+ char *p, *q;
+ qboolean EOS = qfalse;
+
+ Q_strncpyz( buffer, string, MAX_STRING_CHARS );
+
+ p = q = buffer;
+
+ while( *p != '\0' )
+ {
+ //skip to first , or EOS
+ while( *p != ',' && *p != '\0' )
+ p++;
+
+ if( *p == '\0' )
+ EOS = qtrue;
+
+ *p = '\0';
+
+ //strip leading whitespace
+ while( *q == ' ' )
+ q++;
+
+ if( weaponsSize )
+ weapons[ i ] = BG_FindWeaponNumForName( q );
+
+ if( upgradesSize )
+ upgrades[ j ] = BG_FindUpgradeNumForName( q );
+
+ if( weaponsSize && weapons[ i ] == WP_NONE &&
+ upgradesSize && upgrades[ j ] == UP_NONE )
+ Com_Printf( S_COLOR_YELLOW "WARNING: unknown equipment %s\n", q );
+ else if( weaponsSize && weapons[ i ] != WP_NONE )
+ i++;
+ else if( upgradesSize && upgrades[ j ] != UP_NONE )
+ j++;
+
+ if( !EOS )
+ {
+ p++;
+ q = p;
+ }
+ else
+ break;
+
+ if( i == ( weaponsSize - 1 ) || j == ( upgradesSize - 1 ) )
+ break;
+ }
+
+ if( weaponsSize )
+ weapons[ i ] = WP_NONE;
+
+ if( upgradesSize )
+ upgrades[ j ] = UP_NONE;
+}
+
+/*
+===============
+BG_ParseCSVClassList
+===============
+*/
+void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSize )
+{
+ char buffer[ MAX_STRING_CHARS ];
+ int i = 0;
+ char *p, *q;
+ qboolean EOS = qfalse;
+
+ Q_strncpyz( buffer, string, MAX_STRING_CHARS );
+
+ p = q = buffer;
+
+ while( *p != '\0' )
+ {
+ //skip to first , or EOS
+ while( *p != ',' && *p != '\0' )
+ p++;
+
+ if( *p == '\0' )
+ EOS = qtrue;
+
+ *p = '\0';
+
+ //strip leading whitespace
+ while( *q == ' ' )
+ q++;
+
+ classes[ i ] = BG_FindClassNumForName( q );
+
+ if( classes[ i ] == PCL_NONE )
+ Com_Printf( S_COLOR_YELLOW "WARNING: unknown class %s\n", q );
+ else
+ i++;
+
+ if( !EOS )
+ {
+ p++;
+ q = p;
+ }
+ else
+ break;
+ }
+
+ classes[ i ] = PCL_NONE;
+}
+
+/*
+===============
+BG_ParseCSVBuildableList
+===============
+*/
+void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildablesSize )
+{
+ char buffer[ MAX_STRING_CHARS ];
+ int i = 0;
+ char *p, *q;
+ qboolean EOS = qfalse;
+
+ Q_strncpyz( buffer, string, MAX_STRING_CHARS );
+
+ p = q = buffer;
+
+ while( *p != '\0' )
+ {
+ //skip to first , or EOS
+ while( *p != ',' && *p != '\0' )
+ p++;
+
+ if( *p == '\0' )
+ EOS = qtrue;
+
+ *p = '\0';
+
+ //strip leading whitespace
+ while( *q == ' ' )
+ q++;
+
+ buildables[ i ] = BG_FindBuildNumForName( q );
+
+ if( buildables[ i ] == BA_NONE )
+ Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable %s\n", q );
+ else
+ i++;
+
+ if( !EOS )
+ {
+ p++;
+ q = p;
+ }
+ else
+ break;
+ }
+
+ buildables[ i ] = BA_NONE;
+}
+
+/*
+============
+BG_UpgradeClassAvailable
+============
+*/
+qboolean BG_UpgradeClassAvailable( playerState_t *ps )
+{
+ int i;
+ char buffer[ MAX_STRING_CHARS ];
+ stage_t currentStage;
+
+ trap_Cvar_VariableStringBuffer( "g_alienStage", buffer, MAX_STRING_CHARS );
+ currentStage = atoi( buffer );
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ if( BG_ClassCanEvolveFromTo( ps->stats[ STAT_PCLASS ], i,
+ ps->persistant[ PERS_CREDIT ], 0 ) >= 0 &&
+ BG_FindStagesForClass( i, currentStage ) &&
+ BG_ClassIsAllowed( i ) )
+ {
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+typedef struct gameElements_s
+{
+ buildable_t buildables[ BA_NUM_BUILDABLES ];
+ pClass_t classes[ PCL_NUM_CLASSES ];
+ weapon_t weapons[ WP_NUM_WEAPONS ];
+ upgrade_t upgrades[ UP_NUM_UPGRADES ];
+} gameElements_t;
+
+static gameElements_t bg_disabledGameElements;
+
+/*
+============
+BG_InitAllowedGameElements
+============
+*/
+void BG_InitAllowedGameElements( void )
+{
+ char cvar[ MAX_CVAR_VALUE_STRING ];
+
+ trap_Cvar_VariableStringBuffer( "g_disabledEquipment",
+ cvar, MAX_CVAR_VALUE_STRING );
+
+ BG_ParseCSVEquipmentList( cvar,
+ bg_disabledGameElements.weapons, WP_NUM_WEAPONS,
+ bg_disabledGameElements.upgrades, UP_NUM_UPGRADES );
+
+ trap_Cvar_VariableStringBuffer( "g_disabledClasses",
+ cvar, MAX_CVAR_VALUE_STRING );
+
+ BG_ParseCSVClassList( cvar,
+ bg_disabledGameElements.classes, PCL_NUM_CLASSES );
+
+ trap_Cvar_VariableStringBuffer( "g_disabledBuildables",
+ cvar, MAX_CVAR_VALUE_STRING );
+
+ BG_ParseCSVBuildableList( cvar,
+ bg_disabledGameElements.buildables, BA_NUM_BUILDABLES );
+}
+
+/*
+============
+BG_WeaponIsAllowed
+============
+*/
+qboolean BG_WeaponIsAllowed( weapon_t weapon )
+{
+ int i;
+
+ for( i = 0; i < WP_NUM_WEAPONS &&
+ bg_disabledGameElements.weapons[ i ] != WP_NONE; i++ )
+ {
+ if( bg_disabledGameElements.weapons[ i ] == weapon )
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+============
+BG_UpgradeIsAllowed
+============
+*/
+qboolean BG_UpgradeIsAllowed( upgrade_t upgrade )
+{
+ int i;
+
+ for( i = 0; i < UP_NUM_UPGRADES &&
+ bg_disabledGameElements.upgrades[ i ] != UP_NONE; i++ )
+ {
+ if( bg_disabledGameElements.upgrades[ i ] == upgrade )
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+============
+BG_ClassIsAllowed
+============
+*/
+qboolean BG_ClassIsAllowed( pClass_t class )
+{
+ int i;
+
+ for( i = 0; i < PCL_NUM_CLASSES &&
+ bg_disabledGameElements.classes[ i ] != PCL_NONE; i++ )
+ {
+ if( bg_disabledGameElements.classes[ i ] == class )
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+============
+BG_BuildableIsAllowed
+============
+*/
+qboolean BG_BuildableIsAllowed( buildable_t buildable )
+{
+ int i;
+
+ for( i = 0; i < BA_NUM_BUILDABLES &&
+ bg_disabledGameElements.buildables[ i ] != BA_NONE; i++ )
+ {
+ if( bg_disabledGameElements.buildables[ i ] == buildable )
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+============
+BG_ClientListTest
+============
+*/
+qboolean BG_ClientListTest( clientList_t *list, int clientNum )
+{
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list )
+ return qfalse;
+ if( clientNum < 32 )
+ return ( ( list->lo & ( 1 << clientNum ) ) != 0 );
+ else
+ return ( ( list->hi & ( 1 << ( clientNum - 32 ) ) ) != 0 );
+}
+
+/*
+============
+BG_ClientListAdd
+============
+*/
+void BG_ClientListAdd( clientList_t *list, int clientNum )
+{
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list )
+ return;
+ if( clientNum < 32 )
+ list->lo |= ( 1 << clientNum );
+ else
+ list->hi |= ( 1 << ( clientNum - 32 ) );
+}
+
+/*
+============
+BG_ClientListRemove
+============
+*/
+void BG_ClientListRemove( clientList_t *list, int clientNum )
+{
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list )
+ return;
+ if( clientNum < 32 )
+ list->lo &= ~( 1 << clientNum );
+ else
+ list->hi &= ~( 1 << ( clientNum - 32 ) );
+}
+
+/*
+============
+BG_ClientListString
+============
+*/
+char *BG_ClientListString( clientList_t *list )
+{
+ static char s[ 17 ];
+
+ s[ 0 ] = '\0';
+ if( !list )
+ return s;
+ Com_sprintf( s, sizeof( s ), "%08x%08x", list->hi, list->lo );
+ return s;
+}
+
+/*
+============
+BG_ClientListParse
+============
+*/
+void BG_ClientListParse( clientList_t *list, const char *s )
+{
+ if( !list )
+ return;
+ list->lo = 0;
+ list->hi = 0;
+ if( !s )
+ return;
+ if( strlen( s ) != 16 )
+ return;
+ sscanf( s, "%x%x", &list->hi, &list->lo );
+}
+
+
diff --git a/src/game/bg_pmove.c b/src/game/bg_pmove.c
new file mode 100644
index 0000000..542b585
--- /dev/null
+++ b/src/game/bg_pmove.c
@@ -0,0 +1,3540 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// bg_pmove.c -- both games player movement code
+// takes a playerstate and a usercmd as input and returns a modifed playerstate
+
+#include "../qcommon/q_shared.h"
+#include "bg_public.h"
+#include "bg_local.h"
+
+pmove_t *pm;
+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_accelerate = 10.0f;
+float pm_airaccelerate = 1.0f;
+float pm_wateraccelerate = 4.0f;
+float pm_flyaccelerate = 4.0f;
+
+float pm_friction = 6.0f;
+float pm_waterfriction = 1.0f;
+float pm_flightfriction = 6.0f;
+float pm_spectatorfriction = 5.0f;
+
+int c_pmove = 0;
+
+/*
+===============
+PM_AddEvent
+
+===============
+*/
+void PM_AddEvent( int newEvent )
+{
+ BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps );
+}
+
+/*
+===============
+PM_AddTouchEnt
+===============
+*/
+void PM_AddTouchEnt( int entityNum )
+{
+ int i;
+
+ if( entityNum == ENTITYNUM_WORLD )
+ return;
+
+ if( pm->numtouch == MAXTOUCH )
+ return;
+
+ // see if it is already added
+ for( i = 0 ; i < pm->numtouch ; i++ )
+ {
+ if( pm->touchents[ i ] == entityNum )
+ return;
+ }
+
+ // add it
+ pm->touchents[ pm->numtouch ] = entityNum;
+ pm->numtouch++;
+}
+
+/*
+===================
+PM_StartTorsoAnim
+===================
+*/
+static void PM_StartTorsoAnim( int anim )
+{
+ if( pm->ps->pm_type >= PM_DEAD )
+ return;
+
+ pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
+ | anim;
+}
+
+/*
+===================
+PM_StartLegsAnim
+===================
+*/
+static void PM_StartLegsAnim( int anim )
+{
+ if( pm->ps->pm_type >= PM_DEAD )
+ return;
+
+ //legsTimer is clamped too tightly for nonsegmented models
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ {
+ if( pm->ps->legsTimer > 0 )
+ return; // a high priority animation is running
+ }
+ else
+ {
+ if( pm->ps->torsoTimer > 0 )
+ return; // a high priority animation is running
+ }
+
+ pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
+ | anim;
+}
+
+/*
+===================
+PM_ContinueLegsAnim
+===================
+*/
+static void PM_ContinueLegsAnim( int anim )
+{
+ if( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim )
+ return;
+
+ //legsTimer is clamped too tightly for nonsegmented models
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ {
+ if( pm->ps->legsTimer > 0 )
+ return; // a high priority animation is running
+ }
+ else
+ {
+ if( pm->ps->torsoTimer > 0 )
+ return; // a high priority animation is running
+ }
+
+ PM_StartLegsAnim( anim );
+}
+
+/*
+===================
+PM_ContinueTorsoAnim
+===================
+*/
+static void PM_ContinueTorsoAnim( int anim )
+{
+ if( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim )
+ return;
+
+ if( pm->ps->torsoTimer > 0 )
+ return; // a high priority animation is running
+
+ PM_StartTorsoAnim( anim );
+}
+
+/*
+===================
+PM_ForceLegsAnim
+===================
+*/
+static void PM_ForceLegsAnim( int anim )
+{
+ //legsTimer is clamped too tightly for nonsegmented models
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ pm->ps->legsTimer = 0;
+ else
+ pm->ps->torsoTimer = 0;
+
+ PM_StartLegsAnim( anim );
+}
+
+
+/*
+==================
+PM_ClipVelocity
+
+Slide off of the impacting surface
+==================
+*/
+void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce )
+{
+ float backoff;
+ float change;
+ int i;
+
+ backoff = DotProduct( in, normal );
+
+ //Com_Printf( "%1.0f ", backoff );
+
+ if( backoff < 0 )
+ backoff *= overbounce;
+ else
+ backoff /= overbounce;
+
+ for( i = 0; i < 3; i++ )
+ {
+ change = normal[ i ] * backoff;
+ //Com_Printf( "%1.0f ", change );
+ out[ i ] = in[ i ] - change;
+ }
+
+ //Com_Printf( " " );
+}
+
+
+/*
+==================
+PM_Friction
+
+Handles both ground friction and water friction
+==================
+*/
+static void PM_Friction( void )
+{
+ vec3_t vec;
+ float *vel;
+ float speed, newspeed, control;
+ float drop;
+
+ vel = pm->ps->velocity;
+
+ //TA: make sure vertical velocity is NOT set to zero when wall climbing
+ VectorCopy( vel, vec );
+ if( pml.walking && !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) )
+ vec[ 2 ] = 0; // ignore slope movement
+
+ speed = VectorLength( vec );
+
+ if( speed < 1 )
+ {
+ vel[ 0 ] = 0;
+ vel[ 1 ] = 0; // allow sinking underwater
+ // FIXME: still have z friction underwater?
+ return;
+ }
+
+ drop = 0;
+
+ // apply ground friction
+ if( pm->waterlevel <= 1 )
+ {
+ if( ( pml.walking || pml.ladder ) && !( pml.groundTrace.surfaceFlags & SURF_SLICK ) )
+ {
+ // if getting knocked back, no friction
+ if( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) )
+ {
+ float stopSpeed = BG_FindStopSpeedForClass( pm->ps->stats[ STAT_PCLASS ] );
+
+ control = speed < stopSpeed ? stopSpeed : speed;
+ drop += control * BG_FindFrictionForClass( pm->ps->stats[ STAT_PCLASS ] ) * pml.frametime;
+ }
+ }
+ }
+
+ // apply water friction even if just wading
+ if( pm->waterlevel )
+ drop += speed * pm_waterfriction * pm->waterlevel * pml.frametime;
+
+ // apply flying friction
+ if( pm->ps->pm_type == PM_JETPACK )
+ drop += speed * pm_flightfriction * pml.frametime;
+
+ if( pm->ps->pm_type == PM_SPECTATOR )
+ drop += speed * pm_spectatorfriction * pml.frametime;
+
+ // scale the velocity
+ newspeed = speed - drop;
+ if( newspeed < 0 )
+ newspeed = 0;
+
+ newspeed /= speed;
+
+ vel[ 0 ] = vel[ 0 ] * newspeed;
+ vel[ 1 ] = vel[ 1 ] * newspeed;
+ vel[ 2 ] = vel[ 2 ] * newspeed;
+}
+
+
+/*
+==============
+PM_Accelerate
+
+Handles user intended acceleration
+==============
+*/
+static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel )
+{
+#if 1
+ // q2 style
+ int i;
+ float addspeed, accelspeed, currentspeed;
+
+ currentspeed = DotProduct( pm->ps->velocity, wishdir );
+ addspeed = wishspeed - currentspeed;
+ if( addspeed <= 0 )
+ return;
+
+ accelspeed = accel * pml.frametime * wishspeed;
+ if( accelspeed > addspeed )
+ accelspeed = addspeed;
+
+ for( i = 0; i < 3; i++ )
+ pm->ps->velocity[ i ] += accelspeed * wishdir[ i ];
+#else
+ // proper way (avoids strafe jump maxspeed bug), but feels bad
+ vec3_t wishVelocity;
+ vec3_t pushDir;
+ float pushLen;
+ float canPush;
+
+ VectorScale( wishdir, wishspeed, wishVelocity );
+ VectorSubtract( wishVelocity, pm->ps->velocity, pushDir );
+ pushLen = VectorNormalize( pushDir );
+
+ canPush = accel * pml.frametime * wishspeed;
+ if( canPush > pushLen )
+ canPush = pushLen;
+
+ VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity );
+#endif
+}
+
+
+
+/*
+============
+PM_CmdScale
+
+Returns the scale factor to apply to cmd movements
+This allows the clients to use axial -127 to 127 values for all directions
+without getting a sqrt(2) distortion in speed.
+============
+*/
+static float PM_CmdScale( usercmd_t *cmd )
+{
+ int max;
+ float total;
+ float scale;
+ float modifier = 1.0f;
+
+ if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS && pm->ps->pm_type == PM_NORMAL )
+ {
+ if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST )
+ modifier *= HUMAN_SPRINT_MODIFIER;
+ else
+ modifier *= HUMAN_JOG_MODIFIER;
+
+ if( cmd->forwardmove < 0 )
+ {
+ //can't run backwards
+ modifier *= HUMAN_BACK_MODIFIER;
+ }
+ else if( cmd->rightmove )
+ {
+ //can't move that fast sideways
+ modifier *= HUMAN_SIDE_MODIFIER;
+ }
+
+ //must have +ve stamina to jump
+ if( pm->ps->stats[ STAT_STAMINA ] < 0 )
+ cmd->upmove = 0;
+
+ //slow down once stamina depletes
+ if( pm->ps->stats[ STAT_STAMINA ] <= -500 )
+ modifier *= (float)( pm->ps->stats[ STAT_STAMINA ] + 1000 ) / 500.0f;
+
+ if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED )
+ {
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, pm->ps->stats ) ||
+ BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) )
+ modifier *= CREEP_ARMOUR_MODIFIER;
+ else
+ modifier *= CREEP_MODIFIER;
+ }
+ }
+
+ if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE )
+ modifier *= ( 1.0f + ( pm->ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) *
+ ( LEVEL4_CHARGE_SPEED - 1.0f ) );
+
+ //slow player if charging up for a pounce
+ if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) &&
+ cmd->buttons & BUTTON_ATTACK2 )
+ modifier *= LEVEL3_POUNCE_SPEED_MOD;
+
+ //slow the player if slow locked
+ if( pm->ps->stats[ STAT_STATE ] & SS_SLOWLOCKED )
+ modifier *= ABUILDER_BLOB_SPEED_MOD;
+
+ if( pm->ps->pm_type == PM_GRABBED )
+ modifier = 0.0f;
+
+ if( pm->ps->pm_type != PM_SPECTATOR && pm->ps->pm_type != PM_NOCLIP )
+ {
+ if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f )
+ cmd->upmove = 0;
+
+ //prevent speed distortions for non ducking classes
+ if( !( pm->ps->pm_flags & PMF_DUCKED ) && pm->ps->pm_type != PM_JETPACK && cmd->upmove < 0 )
+ cmd->upmove = 0;
+ }
+
+ max = abs( cmd->forwardmove );
+ if( abs( cmd->rightmove ) > max )
+ max = abs( cmd->rightmove );
+
+ if( abs( cmd->upmove ) > max )
+ max = abs( cmd->upmove );
+
+ if( !max )
+ return 0;
+
+ total = sqrt( cmd->forwardmove * cmd->forwardmove
+ + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove );
+
+ scale = (float)pm->ps->speed * max / ( 127.0 * total ) * modifier;
+
+ return scale;
+}
+
+
+/*
+================
+PM_SetMovementDir
+
+Determine the rotation of the legs reletive
+to the facing dir
+================
+*/
+static void PM_SetMovementDir( void )
+{
+ if( pm->cmd.forwardmove || pm->cmd.rightmove )
+ {
+ if( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 )
+ pm->ps->movementDir = 0;
+ else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 )
+ pm->ps->movementDir = 1;
+ else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 )
+ pm->ps->movementDir = 2;
+ else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 )
+ pm->ps->movementDir = 3;
+ else if( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 )
+ pm->ps->movementDir = 4;
+ else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 )
+ pm->ps->movementDir = 5;
+ else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 )
+ pm->ps->movementDir = 6;
+ else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 )
+ pm->ps->movementDir = 7;
+ }
+ else
+ {
+ // if they aren't actively going directly sideways,
+ // change the animation to the diagonal so they
+ // don't stop too crooked
+ if( pm->ps->movementDir == 2 )
+ pm->ps->movementDir = 1;
+ else if( pm->ps->movementDir == 6 )
+ pm->ps->movementDir = 7;
+ }
+}
+
+
+/*
+=============
+PM_CheckCharge
+=============
+*/
+static void PM_CheckCharge( void )
+{
+ if( pm->ps->weapon != WP_ALEVEL4 )
+ return;
+
+ if( pm->cmd.buttons & BUTTON_ATTACK2 &&
+ !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) )
+ {
+ pm->ps->pm_flags &= ~PMF_CHARGE;
+ return;
+ }
+
+ if( pm->ps->stats[ STAT_MISC ] > 0 )
+ pm->ps->pm_flags |= PMF_CHARGE;
+ else
+ pm->ps->pm_flags &= ~PMF_CHARGE;
+}
+
+/*
+=============
+PM_CheckPounce
+=============
+*/
+static qboolean PM_CheckPounce( void )
+{
+ if( pm->ps->weapon != WP_ALEVEL3 &&
+ pm->ps->weapon != WP_ALEVEL3_UPG )
+ return qfalse;
+
+ // we were pouncing, but we've landed
+ if( pm->ps->groundEntityNum != ENTITYNUM_NONE
+ && ( pm->ps->pm_flags & PMF_CHARGE ) )
+ {
+ pm->ps->weaponTime += LEVEL3_POUNCE_TIME;
+ pm->ps->pm_flags &= ~PMF_CHARGE;
+ }
+
+ // we're building up for a pounce
+ if( pm->cmd.buttons & BUTTON_ATTACK2 )
+ return qfalse;
+
+ // already a pounce in progress
+ if( pm->ps->pm_flags & PMF_CHARGE )
+ return qfalse;
+
+ if( pm->ps->stats[ STAT_MISC ] == 0 )
+ return qfalse;
+
+ pml.groundPlane = qfalse; // jumping away
+ pml.walking = qfalse;
+
+ pm->ps->pm_flags |= PMF_CHARGE;
+
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+
+ VectorMA( pm->ps->velocity, pm->ps->stats[ STAT_MISC ], pml.forward, pm->ps->velocity );
+
+ PM_AddEvent( EV_JUMP );
+
+ if( pm->cmd.forwardmove >= 0 )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMP );
+ else
+ PM_ForceLegsAnim( NSPA_JUMP );
+
+ pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMPB );
+ else
+ PM_ForceLegsAnim( NSPA_JUMPBACK );
+
+ pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+
+ pm->pmext->pouncePayload = pm->ps->stats[ STAT_MISC ];
+ pm->ps->stats[ STAT_MISC ] = 0;
+
+ return qtrue;
+}
+
+/*
+=============
+PM_CheckWallJump
+=============
+*/
+static qboolean PM_CheckWallJump( void )
+{
+ vec3_t dir, forward, right;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ float normalFraction = 1.5f;
+ float cmdFraction = 1.0f;
+ float upFraction = 1.5f;
+
+ if( pm->ps->pm_flags & PMF_RESPAWNED )
+ return qfalse; // don't allow jump until all buttons are up
+
+ if( pm->cmd.upmove < 10 )
+ // not holding jump
+ return qfalse;
+
+ if( pm->ps->pm_flags & PMF_TIME_WALLJUMP )
+ return qfalse;
+
+ // must wait for jump to be released
+ if( pm->ps->pm_flags & PMF_JUMP_HELD &&
+ pm->ps->grapplePoint[ 2 ] == 1.0f )
+ {
+ // clear upmove so cmdscale doesn't lower running speed
+ pm->cmd.upmove = 0;
+ return qfalse;
+ }
+
+ pm->ps->pm_flags |= PMF_TIME_WALLJUMP;
+ pm->ps->pm_time = 200;
+
+ pml.groundPlane = qfalse; // jumping away
+ pml.walking = qfalse;
+ pm->ps->pm_flags |= PMF_JUMP_HELD;
+
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+
+ ProjectPointOnPlane( forward, pml.forward, pm->ps->grapplePoint );
+ ProjectPointOnPlane( right, pml.right, pm->ps->grapplePoint );
+
+ VectorScale( pm->ps->grapplePoint, normalFraction, dir );
+
+ if( pm->cmd.forwardmove > 0 )
+ VectorMA( dir, cmdFraction, forward, dir );
+ else if( pm->cmd.forwardmove < 0 )
+ VectorMA( dir, -cmdFraction, forward, dir );
+
+ if( pm->cmd.rightmove > 0 )
+ VectorMA( dir, cmdFraction, right, dir );
+ else if( pm->cmd.rightmove < 0 )
+ VectorMA( dir, -cmdFraction, right, dir );
+
+ VectorMA( dir, upFraction, refNormal, dir );
+ VectorNormalize( dir );
+
+ VectorMA( pm->ps->velocity, BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ),
+ dir, pm->ps->velocity );
+
+ //for a long run of wall jumps the velocity can get pretty large, this caps it
+ if( VectorLength( pm->ps->velocity ) > LEVEL2_WALLJUMP_MAXSPEED )
+ {
+ VectorNormalize( pm->ps->velocity );
+ VectorScale( pm->ps->velocity, LEVEL2_WALLJUMP_MAXSPEED, pm->ps->velocity );
+ }
+
+ PM_AddEvent( EV_JUMP );
+
+ if( pm->cmd.forwardmove >= 0 )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMP );
+ else
+ PM_ForceLegsAnim( NSPA_JUMP );
+
+ pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMPB );
+ else
+ PM_ForceLegsAnim( NSPA_JUMPBACK );
+
+ pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+
+ return qtrue;
+}
+
+/*
+=============
+PM_CheckJump
+=============
+*/
+static qboolean PM_CheckJump( void )
+{
+ if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f )
+ return qfalse;
+
+ if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ return PM_CheckWallJump( );
+
+ //can't jump and pounce at the same time
+ if( ( pm->ps->weapon == WP_ALEVEL3 ||
+ pm->ps->weapon == WP_ALEVEL3_UPG ) &&
+ pm->ps->stats[ STAT_MISC ] > 0 )
+ return qfalse;
+
+ //can't jump and charge at the same time
+ if( ( pm->ps->weapon == WP_ALEVEL4 ) &&
+ pm->ps->stats[ STAT_MISC ] > 0 )
+ return qfalse;
+
+ if( ( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS ) &&
+ ( pm->ps->stats[ STAT_STAMINA ] < 0 ) )
+ return qfalse;
+
+ if( pm->ps->pm_flags & PMF_RESPAWNED )
+ return qfalse; // don't allow jump until all buttons are up
+
+ if( pm->cmd.upmove < 10 )
+ // not holding jump
+ return qfalse;
+
+ //can't jump whilst grabbed
+ if( pm->ps->pm_type == PM_GRABBED )
+ {
+ pm->cmd.upmove = 0;
+ return qfalse;
+ }
+
+ // must wait for jump to be released
+ if( pm->ps->pm_flags & PMF_JUMP_HELD )
+ {
+ // clear upmove so cmdscale doesn't lower running speed
+ pm->cmd.upmove = 0;
+ return qfalse;
+ }
+
+ pml.groundPlane = qfalse; // jumping away
+ pml.walking = qfalse;
+ pm->ps->pm_flags |= PMF_JUMP_HELD;
+
+ //TA: take some stamina off
+ if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS )
+ pm->ps->stats[ STAT_STAMINA ] -= 500;
+
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+
+ //TA: jump away from wall
+ if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ vec3_t normal = { 0, 0, -1 };
+
+ if( !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) )
+ VectorCopy( pm->ps->grapplePoint, normal );
+
+ VectorMA( pm->ps->velocity, BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ),
+ normal, pm->ps->velocity );
+ }
+ else
+ pm->ps->velocity[ 2 ] = BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] );
+
+ PM_AddEvent( EV_JUMP );
+
+ if( pm->cmd.forwardmove >= 0 )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMP );
+ else
+ PM_ForceLegsAnim( NSPA_JUMP );
+
+ pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMPB );
+ else
+ PM_ForceLegsAnim( NSPA_JUMPBACK );
+
+ pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+
+ return qtrue;
+}
+
+/*
+=============
+PM_CheckWaterJump
+=============
+*/
+static qboolean PM_CheckWaterJump( void )
+{
+ vec3_t spot;
+ int cont;
+ vec3_t flatforward;
+
+ if( pm->ps->pm_time )
+ return qfalse;
+
+ // check for water jump
+ if( pm->waterlevel != 2 )
+ return qfalse;
+
+ flatforward[ 0 ] = pml.forward[ 0 ];
+ flatforward[ 1 ] = pml.forward[ 1 ];
+ flatforward[ 2 ] = 0;
+ VectorNormalize( flatforward );
+
+ VectorMA( pm->ps->origin, 30, flatforward, spot );
+ spot[ 2 ] += 4;
+ cont = pm->pointcontents( spot, pm->ps->clientNum );
+
+ if( !( cont & CONTENTS_SOLID ) )
+ return qfalse;
+
+ spot[ 2 ] += 16;
+ cont = pm->pointcontents( spot, pm->ps->clientNum );
+
+ if( cont )
+ return qfalse;
+
+ // jump out of water
+ VectorScale( pml.forward, 200, pm->ps->velocity );
+ pm->ps->velocity[ 2 ] = 350;
+
+ pm->ps->pm_flags |= PMF_TIME_WATERJUMP;
+ pm->ps->pm_time = 2000;
+
+ return qtrue;
+}
+
+//============================================================================
+
+
+/*
+===================
+PM_WaterJumpMove
+
+Flying out of the water
+===================
+*/
+static void PM_WaterJumpMove( void )
+{
+ // waterjump has no control, but falls
+
+ PM_StepSlideMove( qtrue, qfalse );
+
+ pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime;
+ if( pm->ps->velocity[ 2 ] < 0 )
+ {
+ // cancel as soon as we are falling down again
+ pm->ps->pm_flags &= ~PMF_ALL_TIMES;
+ pm->ps->pm_time = 0;
+ }
+}
+
+/*
+===================
+PM_WaterMove
+
+===================
+*/
+static void PM_WaterMove( void )
+{
+ int i;
+ vec3_t wishvel;
+ float wishspeed;
+ vec3_t wishdir;
+ float scale;
+ float vel;
+
+ if( PM_CheckWaterJump( ) )
+ {
+ PM_WaterJumpMove();
+ return;
+ }
+#if 0
+ // jump = head for surface
+ if ( pm->cmd.upmove >= 10 ) {
+ if (pm->ps->velocity[2] > -300) {
+ if ( pm->watertype == CONTENTS_WATER ) {
+ pm->ps->velocity[2] = 100;
+ } else if (pm->watertype == CONTENTS_SLIME) {
+ pm->ps->velocity[2] = 80;
+ } else {
+ pm->ps->velocity[2] = 50;
+ }
+ }
+ }
+#endif
+ PM_Friction( );
+
+ scale = PM_CmdScale( &pm->cmd );
+ //
+ // user intentions
+ //
+ if( !scale )
+ {
+ wishvel[ 0 ] = 0;
+ wishvel[ 1 ] = 0;
+ wishvel[ 2 ] = -60; // sink towards bottom
+ }
+ else
+ {
+ for( i = 0; i < 3; i++ )
+ wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove;
+
+ wishvel[ 2 ] += scale * pm->cmd.upmove;
+ }
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+
+ if( wishspeed > pm->ps->speed * pm_swimScale )
+ wishspeed = pm->ps->speed * pm_swimScale;
+
+ PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate );
+
+ // make sure we can go up slopes easily under water
+ if( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 )
+ {
+ vel = VectorLength( pm->ps->velocity );
+ // slide along the ground plane
+ PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
+ pm->ps->velocity, OVERCLIP );
+
+ VectorNormalize( pm->ps->velocity );
+ VectorScale( pm->ps->velocity, vel, pm->ps->velocity );
+ }
+
+ PM_SlideMove( qfalse );
+}
+
+/*
+===================
+PM_JetPackMove
+
+Only with the jetpack
+===================
+*/
+static void PM_JetPackMove( void )
+{
+ int i;
+ vec3_t wishvel;
+ float wishspeed;
+ vec3_t wishdir;
+ float scale;
+
+ //normal slowdown
+ PM_Friction( );
+
+ scale = PM_CmdScale( &pm->cmd );
+
+ // user intentions
+ for( i = 0; i < 2; i++ )
+ wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove;
+
+ if( pm->cmd.upmove > 0.0f )
+ wishvel[ 2 ] = JETPACK_FLOAT_SPEED;
+ else if( pm->cmd.upmove < 0.0f )
+ wishvel[ 2 ] = -JETPACK_SINK_SPEED;
+ else
+ wishvel[ 2 ] = 0.0f;
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+
+ PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate );
+
+ PM_StepSlideMove( qfalse, qfalse );
+
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_LAND );
+ else
+ PM_ContinueLegsAnim( NSPA_LAND );
+}
+
+
+
+
+/*
+===================
+PM_FlyMove
+
+Only with the flight powerup
+===================
+*/
+static void PM_FlyMove( void )
+{
+ int i;
+ vec3_t wishvel;
+ float wishspeed;
+ vec3_t wishdir;
+ float scale;
+
+ // normal slowdown
+ PM_Friction( );
+
+ scale = PM_CmdScale( &pm->cmd );
+ //
+ // user intentions
+ //
+ if( !scale )
+ {
+ wishvel[ 0 ] = 0;
+ wishvel[ 1 ] = 0;
+ wishvel[ 2 ] = 0;
+ }
+ else
+ {
+ for( i = 0; i < 3; i++ )
+ wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove;
+
+ wishvel[ 2 ] += scale * pm->cmd.upmove;
+ }
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+
+ PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate );
+
+ PM_StepSlideMove( qfalse, qfalse );
+}
+
+
+/*
+===================
+PM_AirMove
+
+===================
+*/
+static void PM_AirMove( void )
+{
+ int i;
+ vec3_t wishvel;
+ float fmove, smove;
+ vec3_t wishdir;
+ float wishspeed;
+ float scale;
+ usercmd_t cmd;
+
+ PM_Friction( );
+
+ fmove = pm->cmd.forwardmove;
+ smove = pm->cmd.rightmove;
+
+ cmd = pm->cmd;
+ scale = PM_CmdScale( &cmd );
+
+ // set the movementDir so clients can rotate the legs for strafing
+ PM_SetMovementDir( );
+
+ // project moves down to flat plane
+ pml.forward[ 2 ] = 0;
+ pml.right[ 2 ] = 0;
+ VectorNormalize( pml.forward );
+ VectorNormalize( pml.right );
+
+ for( i = 0; i < 2; i++ )
+ wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove;
+
+ wishvel[ 2 ] = 0;
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+ wishspeed *= scale;
+
+ // not on ground, so little effect on velocity
+ PM_Accelerate( wishdir, wishspeed,
+ BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ) );
+
+ // we may have a ground plane that is very steep, even
+ // though we don't have a groundentity
+ // slide along the steep plane
+ if( pml.groundPlane )
+ PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
+ pm->ps->velocity, OVERCLIP );
+
+ PM_StepSlideMove( qtrue, qfalse );
+}
+
+/*
+===================
+PM_ClimbMove
+
+===================
+*/
+static void PM_ClimbMove( void )
+{
+ int i;
+ vec3_t wishvel;
+ float fmove, smove;
+ vec3_t wishdir;
+ float wishspeed;
+ float scale;
+ usercmd_t cmd;
+ float accelerate;
+ float vel;
+
+ if( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 )
+ {
+ // begin swimming
+ PM_WaterMove( );
+ return;
+ }
+
+
+ if( PM_CheckJump( ) || PM_CheckPounce( ) )
+ {
+ // jumped away
+ if( pm->waterlevel > 1 )
+ PM_WaterMove( );
+ else
+ PM_AirMove( );
+
+ return;
+ }
+
+ PM_Friction( );
+
+ fmove = pm->cmd.forwardmove;
+ smove = pm->cmd.rightmove;
+
+ cmd = pm->cmd;
+ scale = PM_CmdScale( &cmd );
+
+ // set the movementDir so clients can rotate the legs for strafing
+ PM_SetMovementDir( );
+
+ // project the forward and right directions onto the ground plane
+ PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP );
+ PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP );
+ //
+ VectorNormalize( pml.forward );
+ VectorNormalize( pml.right );
+
+ for( i = 0; i < 3; i++ )
+ wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove;
+
+ // when going up or down slopes the wish velocity should Not be zero
+// wishvel[2] = 0;
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+ wishspeed *= scale;
+
+ // clamp the speed lower if ducking
+ if( pm->ps->pm_flags & PMF_DUCKED )
+ {
+ if( wishspeed > pm->ps->speed * pm_duckScale )
+ wishspeed = pm->ps->speed * pm_duckScale;
+ }
+
+ // clamp the speed lower if wading or walking on the bottom
+ if( pm->waterlevel )
+ {
+ float waterScale;
+
+ waterScale = pm->waterlevel / 3.0;
+ waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale;
+ if( wishspeed > pm->ps->speed * waterScale )
+ wishspeed = pm->ps->speed * waterScale;
+ }
+
+ // when a player gets hit, they temporarily lose
+ // full control, which allows them to be moved a bit
+ if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK )
+ accelerate = BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] );
+ else
+ accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] );
+
+ PM_Accelerate( wishdir, wishspeed, accelerate );
+
+ if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK )
+ pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime;
+
+ vel = VectorLength( pm->ps->velocity );
+
+ // slide along the ground plane
+ PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
+ pm->ps->velocity, OVERCLIP );
+
+ // don't decrease velocity when going up or down a slope
+ VectorNormalize( pm->ps->velocity );
+ VectorScale( pm->ps->velocity, vel, pm->ps->velocity );
+
+ // don't do anything if standing still
+ if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] && !pm->ps->velocity[ 2 ] )
+ return;
+
+ PM_StepSlideMove( qfalse, qfalse );
+}
+
+
+/*
+===================
+PM_WalkMove
+
+===================
+*/
+static void PM_WalkMove( void )
+{
+ int i;
+ vec3_t wishvel;
+ float fmove, smove;
+ vec3_t wishdir;
+ float wishspeed;
+ float scale;
+ usercmd_t cmd;
+ float accelerate;
+
+ if( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 )
+ {
+ // begin swimming
+ PM_WaterMove( );
+ return;
+ }
+
+
+ if( PM_CheckJump( ) || PM_CheckPounce( ) )
+ {
+ // jumped away
+ if( pm->waterlevel > 1 )
+ PM_WaterMove( );
+ else
+ PM_AirMove( );
+
+ return;
+ }
+
+ //charging
+ PM_CheckCharge( );
+
+ PM_Friction( );
+
+ fmove = pm->cmd.forwardmove;
+ smove = pm->cmd.rightmove;
+
+ cmd = pm->cmd;
+ scale = PM_CmdScale( &cmd );
+
+ // set the movementDir so clients can rotate the legs for strafing
+ PM_SetMovementDir( );
+
+ // project moves down to flat plane
+ pml.forward[ 2 ] = 0;
+ pml.right[ 2 ] = 0;
+
+ // project the forward and right directions onto the ground plane
+ PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP );
+ PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP );
+ //
+ VectorNormalize( pml.forward );
+ VectorNormalize( pml.right );
+
+ for( i = 0; i < 3; i++ )
+ wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove;
+
+ // when going up or down slopes the wish velocity should Not be zero
+// wishvel[2] = 0;
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+ wishspeed *= scale;
+
+ // clamp the speed lower if ducking
+ if( pm->ps->pm_flags & PMF_DUCKED )
+ {
+ if( wishspeed > pm->ps->speed * pm_duckScale )
+ wishspeed = pm->ps->speed * pm_duckScale;
+ }
+
+ // clamp the speed lower if wading or walking on the bottom
+ if( pm->waterlevel )
+ {
+ float waterScale;
+
+ waterScale = pm->waterlevel / 3.0;
+ waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale;
+ if( wishspeed > pm->ps->speed * waterScale )
+ wishspeed = pm->ps->speed * waterScale;
+ }
+
+ // when a player gets hit, they temporarily lose
+ // full control, which allows them to be moved a bit
+ if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK )
+ accelerate = BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] );
+ else
+ accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] );
+
+ PM_Accelerate( wishdir, wishspeed, accelerate );
+
+ //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]);
+ //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity));
+
+ if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK )
+ pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime;
+ else
+ {
+ // don't reset the z velocity for slopes
+// pm->ps->velocity[2] = 0;
+ }
+
+ // slide along the ground plane
+ PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
+ pm->ps->velocity, OVERCLIP );
+
+ // don't do anything if standing still
+ if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] )
+ return;
+
+ PM_StepSlideMove( qfalse, qfalse );
+
+ //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity));
+
+}
+
+
+/*
+===================
+PM_LadderMove
+
+Basically a rip of PM_WaterMove with a few changes
+===================
+*/
+static void PM_LadderMove( void )
+{
+ int i;
+ vec3_t wishvel;
+ float wishspeed;
+ vec3_t wishdir;
+ float scale;
+ float vel;
+
+ PM_Friction( );
+
+ scale = PM_CmdScale( &pm->cmd );
+
+ for( i = 0; i < 3; i++ )
+ wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove;
+
+ wishvel[ 2 ] += scale * pm->cmd.upmove;
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+
+ if( wishspeed > pm->ps->speed * pm_swimScale )
+ wishspeed = pm->ps->speed * pm_swimScale;
+
+ PM_Accelerate( wishdir, wishspeed, pm_accelerate );
+
+ //slanty ladders
+ if( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0.0f )
+ {
+ vel = VectorLength( pm->ps->velocity );
+
+ // slide along the ground plane
+ PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
+ pm->ps->velocity, OVERCLIP );
+
+ VectorNormalize( pm->ps->velocity );
+ VectorScale( pm->ps->velocity, vel, pm->ps->velocity );
+ }
+
+ PM_SlideMove( qfalse );
+}
+
+
+/*
+=============
+PM_CheckLadder
+
+Check to see if the player is on a ladder or not
+=============
+*/
+static void PM_CheckLadder( void )
+{
+ vec3_t forward, end;
+ trace_t trace;
+
+ //test if class can use ladders
+ if( !BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_CANUSELADDERS ) )
+ {
+ pml.ladder = qfalse;
+ return;
+ }
+
+ VectorCopy( pml.forward, forward );
+ forward[ 2 ] = 0.0f;
+
+ VectorMA( pm->ps->origin, 1.0f, forward, end );
+
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, MASK_PLAYERSOLID );
+
+ if( ( trace.fraction < 1.0f ) && ( trace.surfaceFlags & SURF_LADDER ) )
+ pml.ladder = qtrue;
+ else
+ pml.ladder = qfalse;
+}
+
+
+/*
+==============
+PM_DeadMove
+==============
+*/
+static void PM_DeadMove( void )
+{
+ float forward;
+
+ if( !pml.walking )
+ return;
+
+ // extra friction
+
+ forward = VectorLength( pm->ps->velocity );
+ forward -= 20;
+
+ if( forward <= 0 )
+ VectorClear( pm->ps->velocity );
+ else
+ {
+ VectorNormalize( pm->ps->velocity );
+ VectorScale( pm->ps->velocity, forward, pm->ps->velocity );
+ }
+}
+
+
+/*
+===============
+PM_NoclipMove
+===============
+*/
+static void PM_NoclipMove( void )
+{
+ float speed, drop, friction, control, newspeed;
+ int i;
+ vec3_t wishvel;
+ float fmove, smove;
+ vec3_t wishdir;
+ float wishspeed;
+ float scale;
+
+ pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
+
+ // friction
+
+ speed = VectorLength( pm->ps->velocity );
+
+ if( speed < 1 )
+ {
+ VectorCopy( vec3_origin, pm->ps->velocity );
+ }
+ else
+ {
+ drop = 0;
+
+ friction = pm_friction * 1.5; // extra friction
+ control = speed < pm_stopspeed ? pm_stopspeed : speed;
+ drop += control * friction * pml.frametime;
+
+ // scale the velocity
+ newspeed = speed - drop;
+
+ if( newspeed < 0 )
+ newspeed = 0;
+
+ newspeed /= speed;
+
+ VectorScale( pm->ps->velocity, newspeed, pm->ps->velocity );
+ }
+
+ // accelerate
+ scale = PM_CmdScale( &pm->cmd );
+
+ fmove = pm->cmd.forwardmove;
+ smove = pm->cmd.rightmove;
+
+ for( i = 0; i < 3; i++ )
+ wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove;
+
+ wishvel[ 2 ] += pm->cmd.upmove;
+
+ VectorCopy( wishvel, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+ wishspeed *= scale;
+
+ PM_Accelerate( wishdir, wishspeed, pm_accelerate );
+
+ // move
+ VectorMA( pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin );
+}
+
+//============================================================================
+
+/*
+================
+PM_FootstepForSurface
+
+Returns an event number apropriate for the groundsurface
+================
+*/
+static int PM_FootstepForSurface( void )
+{
+ //TA:
+ if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED )
+ return EV_FOOTSTEP_SQUELCH;
+
+ if( pml.groundTrace.surfaceFlags & SURF_NOSTEPS )
+ return 0;
+
+ if( pml.groundTrace.surfaceFlags & SURF_METALSTEPS )
+ return EV_FOOTSTEP_METAL;
+
+ return EV_FOOTSTEP;
+}
+
+
+/*
+=================
+PM_CrashLand
+
+Check for hard landings that generate sound events
+=================
+*/
+static void PM_CrashLand( void )
+{
+ float delta;
+ float dist;
+ float vel, acc;
+ float t;
+ float a, b, c, den;
+
+ // decide which landing animation to use
+ if( pm->ps->pm_flags & PMF_BACKWARDS_JUMP )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_LANDB );
+ else
+ PM_ForceLegsAnim( NSPA_LANDBACK );
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_LAND );
+ else
+ PM_ForceLegsAnim( NSPA_LAND );
+ }
+
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ pm->ps->legsTimer = TIMER_LAND;
+ else
+ pm->ps->torsoTimer = TIMER_LAND;
+
+ // calculate the exact velocity on landing
+ dist = pm->ps->origin[ 2 ] - pml.previous_origin[ 2 ];
+ vel = pml.previous_velocity[ 2 ];
+ acc = -pm->ps->gravity;
+
+ a = acc / 2;
+ b = vel;
+ c = -dist;
+
+ den = b * b - 4 * a * c;
+ if( den < 0 )
+ return;
+
+ t = (-b - sqrt( den ) ) / ( 2 * a );
+
+ delta = vel + t * acc;
+ delta = delta*delta * 0.0001;
+
+ // ducking while falling doubles damage
+ if( pm->ps->pm_flags & PMF_DUCKED )
+ delta *= 2;
+
+ // never take falling damage if completely underwater
+ if( pm->waterlevel == 3 )
+ return;
+
+ // reduce falling damage if there is standing water
+ if( pm->waterlevel == 2 )
+ delta *= 0.25;
+
+ if( pm->waterlevel == 1 )
+ delta *= 0.5;
+
+ if( delta < 1 )
+ return;
+
+ // create a local entity event to play the sound
+
+ // SURF_NODAMAGE is used for bounce pads where you don't ever
+ // want to take damage or play a crunch sound
+ if( !( pml.groundTrace.surfaceFlags & SURF_NODAMAGE ) )
+ {
+ pm->ps->stats[ STAT_FALLDIST ] = delta;
+
+ if( delta > AVG_FALL_DISTANCE )
+ {
+ PM_AddEvent( EV_FALL_FAR );
+ }
+ else if( delta > MIN_FALL_DISTANCE )
+ {
+ // 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 )
+ PM_AddEvent( EV_FALL_SHORT );
+ else
+ PM_AddEvent( PM_FootstepForSurface( ) );
+ }
+ }
+
+ // start footstep cycle over
+ pm->ps->bobCycle = 0;
+}
+
+
+/*
+=============
+PM_CorrectAllSolid
+=============
+*/
+static int PM_CorrectAllSolid( trace_t *trace )
+{
+ int i, j, k;
+ vec3_t point;
+
+ if( pm->debugLevel )
+ Com_Printf("%i:allsolid\n", c_pmove);
+
+ // jitter around
+ for( i = -1; i <= 1; i++ )
+ {
+ for( j = -1; j <= 1; j++ )
+ {
+ for( k = -1; k <= 1; k++ )
+ {
+ VectorCopy( pm->ps->origin, point );
+ point[ 0 ] += (float)i;
+ point[ 1 ] += (float)j;
+ point[ 2 ] += (float)k;
+ pm->trace( trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+
+ if( !trace->allsolid )
+ {
+ point[ 0 ] = pm->ps->origin[ 0 ];
+ point[ 1 ] = pm->ps->origin[ 1 ];
+ point[ 2 ] = pm->ps->origin[ 2 ] - 0.25;
+
+ pm->trace( trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+ pml.groundTrace = *trace;
+ return qtrue;
+ }
+ }
+ }
+ }
+
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+
+ return qfalse;
+}
+
+
+/*
+=============
+PM_GroundTraceMissed
+
+The ground trace didn't hit a surface, so we are in freefall
+=============
+*/
+static void PM_GroundTraceMissed( void )
+{
+ trace_t trace;
+ vec3_t point;
+
+ if( pm->ps->groundEntityNum != ENTITYNUM_NONE )
+ {
+ // we just transitioned into freefall
+ if( pm->debugLevel )
+ Com_Printf( "%i:lift\n", c_pmove );
+
+ // if they aren't in a jumping animation and the ground is a ways away, force into it
+ // if we didn't do the trace, the player would be backflipping down staircases
+ VectorCopy( pm->ps->origin, point );
+ point[ 2 ] -= 64.0f;
+
+ pm->trace( &trace, pm->ps->origin, NULL, NULL, point, pm->ps->clientNum, pm->tracemask );
+ if( trace.fraction == 1.0f )
+ {
+ if( pm->cmd.forwardmove >= 0 )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMP );
+ else
+ PM_ForceLegsAnim( NSPA_JUMP );
+
+ pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMPB );
+ else
+ PM_ForceLegsAnim( NSPA_JUMPBACK );
+
+ pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+ }
+ }
+
+ if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) )
+ {
+ if( pm->ps->velocity[ 2 ] < FALLING_THRESHOLD && pml.previous_velocity[ 2 ] >= FALLING_THRESHOLD )
+ PM_AddEvent( EV_FALLING );
+ }
+
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+}
+
+
+/*
+=============
+PM_GroundClimbTrace
+=============
+*/
+static void PM_GroundClimbTrace( void )
+{
+ vec3_t surfNormal, movedir, lookdir, point;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+ vec3_t toAngles, surfAngles;
+ trace_t trace;
+ int i;
+
+ //used for delta correction
+ vec3_t traceCROSSsurf, traceCROSSref, surfCROSSref;
+ float traceDOTsurf, traceDOTref, surfDOTref, rTtDOTrTsTt;
+ float traceANGsurf, traceANGref, surfANGref;
+ vec3_t horizontal = { 1.0f, 0.0f, 0.0f }; //arbituary vector perpendicular to refNormal
+ vec3_t refTOtrace, refTOsurfTOtrace, tempVec;
+ int rTtANGrTsTt;
+ float ldDOTtCs, d;
+ vec3_t abc;
+
+ //TA: If we're on the ceiling then grapplePoint is a rotation normal.. otherwise its a surface normal.
+ // would have been nice if Carmack had left a few random variables in the ps struct for mod makers
+ if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorCopy( ceilingNormal, surfNormal );
+ else
+ VectorCopy( pm->ps->grapplePoint, surfNormal );
+
+ //construct a vector which reflects the direction the player is looking wrt the surface normal
+ ProjectPointOnPlane( movedir, pml.forward, surfNormal );
+ VectorNormalize( movedir );
+
+ VectorCopy( movedir, lookdir );
+
+ if( pm->cmd.forwardmove < 0 )
+ VectorNegate( movedir, movedir );
+
+ //allow strafe transitions
+ if( pm->cmd.rightmove )
+ {
+ VectorCopy( pml.right, movedir );
+
+ if( pm->cmd.rightmove < 0 )
+ VectorNegate( movedir, movedir );
+ }
+
+ for( i = 0; i <= 4; i++ )
+ {
+ switch ( i )
+ {
+ case 0:
+ //we are going to step this frame so skip the transition test
+ if( PM_PredictStepMove( ) )
+ continue;
+
+ //trace into direction we are moving
+ VectorMA( pm->ps->origin, 0.25f, movedir, point );
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+ break;
+
+ case 1:
+ //trace straight down anto "ground" surface
+ VectorMA( pm->ps->origin, -0.25f, surfNormal, point );
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+ break;
+
+ case 2:
+ if( pml.groundPlane != qfalse && PM_PredictStepMove( ) )
+ {
+ //step down
+ VectorMA( pm->ps->origin, -STEPSIZE, surfNormal, point );
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+ }
+ else
+ continue;
+ break;
+
+ case 3:
+ //trace "underneath" BBOX so we can traverse angles > 180deg
+ if( pml.groundPlane != qfalse )
+ {
+ VectorMA( pm->ps->origin, -16.0f, surfNormal, point );
+ VectorMA( point, -16.0f, movedir, point );
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+ }
+ else
+ continue;
+ break;
+
+ case 4:
+ //fall back so we don't have to modify PM_GroundTrace too much
+ VectorCopy( pm->ps->origin, point );
+ point[ 2 ] = pm->ps->origin[ 2 ] - 0.25f;
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+ break;
+ }
+
+ //if we hit something
+ if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) &&
+ !( trace.entityNum != ENTITYNUM_WORLD && i != 4 ) )
+ {
+ if( i == 2 || i == 3 )
+ {
+ if( i == 2 )
+ PM_StepEvent( pm->ps->origin, trace.endpos, surfNormal );
+
+ VectorCopy( trace.endpos, pm->ps->origin );
+ }
+
+ //calculate a bunch of stuff...
+ CrossProduct( trace.plane.normal, surfNormal, traceCROSSsurf );
+ VectorNormalize( traceCROSSsurf );
+
+ CrossProduct( trace.plane.normal, refNormal, traceCROSSref );
+ VectorNormalize( traceCROSSref );
+
+ CrossProduct( surfNormal, refNormal, surfCROSSref );
+ VectorNormalize( surfCROSSref );
+
+ //calculate angle between surf and trace
+ traceDOTsurf = DotProduct( trace.plane.normal, surfNormal );
+ traceANGsurf = RAD2DEG( acos( traceDOTsurf ) );
+
+ if( traceANGsurf > 180.0f )
+ traceANGsurf -= 180.0f;
+
+ //calculate angle between trace and ref
+ traceDOTref = DotProduct( trace.plane.normal, refNormal );
+ traceANGref = RAD2DEG( acos( traceDOTref ) );
+
+ if( traceANGref > 180.0f )
+ traceANGref -= 180.0f;
+
+ //calculate angle between surf and ref
+ surfDOTref = DotProduct( surfNormal, refNormal );
+ surfANGref = RAD2DEG( acos( surfDOTref ) );
+
+ if( surfANGref > 180.0f )
+ surfANGref -= 180.0f;
+
+ //if the trace result and old surface normal are different then we must have transided to a new
+ //surface... do some stuff...
+ if( !VectorCompare( trace.plane.normal, surfNormal ) )
+ {
+ //if the trace result or the old vector is not the floor or ceiling correct the YAW angle
+ if( !VectorCompare( trace.plane.normal, refNormal ) && !VectorCompare( surfNormal, refNormal ) &&
+ !VectorCompare( trace.plane.normal, ceilingNormal ) && !VectorCompare( surfNormal, ceilingNormal ) )
+ {
+ //behold the evil mindfuck from hell
+ //it has fucked mind like nothing has fucked mind before
+
+ //calculate reference rotated through to trace plane
+ RotatePointAroundVector( refTOtrace, traceCROSSref, horizontal, -traceANGref );
+
+ //calculate reference rotated through to surf plane then to trace plane
+ RotatePointAroundVector( tempVec, surfCROSSref, horizontal, -surfANGref );
+ RotatePointAroundVector( refTOsurfTOtrace, traceCROSSsurf, tempVec, -traceANGsurf );
+
+ //calculate angle between refTOtrace and refTOsurfTOtrace
+ rTtDOTrTsTt = DotProduct( refTOtrace, refTOsurfTOtrace );
+ rTtANGrTsTt = ANGLE2SHORT( RAD2DEG( acos( rTtDOTrTsTt ) ) );
+
+ if( rTtANGrTsTt > 32768 )
+ rTtANGrTsTt -= 32768;
+
+ CrossProduct( refTOtrace, refTOsurfTOtrace, tempVec );
+ VectorNormalize( tempVec );
+ if( DotProduct( trace.plane.normal, tempVec ) > 0.0f )
+ rTtANGrTsTt = -rTtANGrTsTt;
+
+ //phew! - correct the angle
+ pm->ps->delta_angles[ YAW ] -= rTtANGrTsTt;
+ }
+
+ //construct a plane dividing the surf and trace normals
+ CrossProduct( traceCROSSsurf, surfNormal, abc );
+ VectorNormalize( abc );
+ d = DotProduct( abc, pm->ps->origin );
+
+ //construct a point representing where the player is looking
+ VectorAdd( pm->ps->origin, lookdir, point );
+
+ //check whether point is on one side of the plane, if so invert the correction angle
+ if( ( abc[ 0 ] * point[ 0 ] + abc[ 1 ] * point[ 1 ] + abc[ 2 ] * point[ 2 ] - d ) > 0 )
+ traceANGsurf = -traceANGsurf;
+
+ //find the . product of the lookdir and traceCROSSsurf
+ if( ( ldDOTtCs = DotProduct( lookdir, traceCROSSsurf ) ) < 0.0f )
+ {
+ VectorInverse( traceCROSSsurf );
+ ldDOTtCs = DotProduct( lookdir, traceCROSSsurf );
+ }
+
+ //set the correction angle
+ traceANGsurf *= 1.0f - ldDOTtCs;
+
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGFOLLOW ) )
+ {
+ //correct the angle
+ pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( traceANGsurf );
+ }
+
+ //transition from wall to ceiling
+ //normal for subsequent viewangle rotations
+ if( VectorCompare( trace.plane.normal, ceilingNormal ) )
+ {
+ CrossProduct( surfNormal, trace.plane.normal, pm->ps->grapplePoint );
+ VectorNormalize( pm->ps->grapplePoint );
+ pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBINGCEILING;
+ }
+
+ //transition from ceiling to wall
+ //we need to do some different angle correction here cos GPISROTVEC
+ if( VectorCompare( surfNormal, ceilingNormal ) )
+ {
+ vectoangles( trace.plane.normal, toAngles );
+ vectoangles( pm->ps->grapplePoint, surfAngles );
+
+ pm->ps->delta_angles[ 1 ] -= ANGLE2SHORT( ( ( surfAngles[ 1 ] - toAngles[ 1 ] ) * 2 ) - 180.0f );
+ }
+ }
+
+ pml.groundTrace = trace;
+
+ //so everything knows where we're wallclimbing (ie client side)
+ pm->ps->eFlags |= EF_WALLCLIMB;
+
+ //if we're not stuck to the ceiling then set grapplePoint to be a surface normal
+ if( !VectorCompare( trace.plane.normal, ceilingNormal ) )
+ {
+ //so we know what surface we're stuck to
+ VectorCopy( trace.plane.normal, pm->ps->grapplePoint );
+ pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
+ }
+
+ //IMPORTANT: break out of the for loop if we've hit something
+ break;
+ }
+ else if( trace.allsolid )
+ {
+ // do something corrective if the trace starts in a solid...
+ if( !PM_CorrectAllSolid( &trace ) )
+ return;
+ }
+ }
+
+ if( trace.fraction >= 1.0f )
+ {
+ // if the trace didn't hit anything, we are in free fall
+ PM_GroundTraceMissed( );
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+ pm->ps->eFlags &= ~EF_WALLCLIMB;
+
+ //just transided from ceiling to floor... apply delta correction
+ if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ {
+ vec3_t forward, rotated, angles;
+
+ AngleVectors( pm->ps->viewangles, forward, NULL, NULL );
+
+ RotatePointAroundVector( rotated, pm->ps->grapplePoint, forward, 180.0f );
+ vectoangles( rotated, angles );
+
+ pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] );
+ }
+
+ pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
+
+ //we get very bizarre effects if we don't do this :0
+ VectorCopy( refNormal, pm->ps->grapplePoint );
+ return;
+ }
+
+ pml.groundPlane = qtrue;
+ pml.walking = qtrue;
+
+ // hitting solid ground will end a waterjump
+ if( pm->ps->pm_flags & PMF_TIME_WATERJUMP )
+ {
+ pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND);
+ pm->ps->pm_time = 0;
+ }
+
+ pm->ps->groundEntityNum = trace.entityNum;
+
+ // don't reset the z velocity for slopes
+// pm->ps->velocity[2] = 0;
+
+ PM_AddTouchEnt( trace.entityNum );
+}
+
+
+/*
+=============
+PM_GroundTrace
+=============
+*/
+static void PM_GroundTrace( void )
+{
+ vec3_t point;
+ vec3_t movedir;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ trace_t trace;
+
+ if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) )
+ {
+ if( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGTOGGLE )
+ {
+ //toggle wall climbing if holding crouch
+ if( pm->cmd.upmove < 0 && !( pm->ps->pm_flags & PMF_CROUCH_HELD ) )
+ {
+ if( !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) )
+ pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBING;
+ else if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
+
+ pm->ps->pm_flags |= PMF_CROUCH_HELD;
+ }
+ else if( pm->cmd.upmove >= 0 )
+ pm->ps->pm_flags &= ~PMF_CROUCH_HELD;
+ }
+ else
+ {
+ if( pm->cmd.upmove < 0 )
+ pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBING;
+ else if( pm->cmd.upmove >= 0 )
+ pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
+ }
+
+ if( pm->ps->pm_type == PM_DEAD )
+ pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
+
+ if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ PM_GroundClimbTrace( );
+ return;
+ }
+
+ //just transided from ceiling to floor... apply delta correction
+ if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ {
+ vec3_t forward, rotated, angles;
+
+ AngleVectors( pm->ps->viewangles, forward, NULL, NULL );
+
+ RotatePointAroundVector( rotated, pm->ps->grapplePoint, forward, 180.0f );
+ vectoangles( rotated, angles );
+
+ pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] );
+ }
+ }
+
+ pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
+ pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
+ pm->ps->eFlags &= ~EF_WALLCLIMB;
+
+ point[ 0 ] = pm->ps->origin[ 0 ];
+ point[ 1 ] = pm->ps->origin[ 1 ];
+ point[ 2 ] = pm->ps->origin[ 2 ] - 0.25f;
+
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+
+ pml.groundTrace = trace;
+
+ // do something corrective if the trace starts in a solid...
+ if( trace.allsolid )
+ if( !PM_CorrectAllSolid( &trace ) )
+ return;
+
+ //make sure that the surfNormal is reset to the ground
+ VectorCopy( refNormal, pm->ps->grapplePoint );
+
+ // if the trace didn't hit anything, we are in free fall
+ if( trace.fraction == 1.0f )
+ {
+ qboolean steppedDown = qfalse;
+
+ // try to step down
+ if( pml.groundPlane != qfalse && PM_PredictStepMove( ) )
+ {
+ //step down
+ point[ 0 ] = pm->ps->origin[ 0 ];
+ point[ 1 ] = pm->ps->origin[ 1 ];
+ point[ 2 ] = pm->ps->origin[ 2 ] - STEPSIZE;
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+
+ //if we hit something
+ if( trace.fraction < 1.0f )
+ {
+ PM_StepEvent( pm->ps->origin, trace.endpos, refNormal );
+ VectorCopy( trace.endpos, pm->ps->origin );
+ steppedDown = qtrue;
+ }
+ }
+
+ if( !steppedDown )
+ {
+ PM_GroundTraceMissed( );
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+
+ if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ {
+ ProjectPointOnPlane( movedir, pml.forward, refNormal );
+ VectorNormalize( movedir );
+
+ if( pm->cmd.forwardmove < 0 )
+ VectorNegate( movedir, movedir );
+
+ //allow strafe transitions
+ if( pm->cmd.rightmove )
+ {
+ VectorCopy( pml.right, movedir );
+
+ if( pm->cmd.rightmove < 0 )
+ VectorNegate( movedir, movedir );
+ }
+
+ //trace into direction we are moving
+ VectorMA( pm->ps->origin, 0.25f, movedir, point );
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
+
+ if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) &&
+ ( trace.entityNum == ENTITYNUM_WORLD ) )
+ {
+ if( !VectorCompare( trace.plane.normal, pm->ps->grapplePoint ) )
+ {
+ VectorCopy( trace.plane.normal, pm->ps->grapplePoint );
+ PM_CheckWallJump( );
+ }
+ }
+ }
+
+ return;
+ }
+ }
+
+ // check if getting thrown off the ground
+ if( pm->ps->velocity[ 2 ] > 0.0f && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10.0f )
+ {
+ if( pm->debugLevel )
+ Com_Printf( "%i:kickoff\n", c_pmove );
+
+ // go into jump animation
+ if( pm->cmd.forwardmove >= 0 )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMP );
+ else
+ PM_ForceLegsAnim( NSPA_JUMP );
+
+ pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ForceLegsAnim( LEGS_JUMPB );
+ else
+ PM_ForceLegsAnim( NSPA_JUMPBACK );
+
+ pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+ return;
+ }
+
+ // slopes that are too steep will not be considered onground
+ if( trace.plane.normal[ 2 ] < MIN_WALK_NORMAL )
+ {
+ if( pm->debugLevel )
+ Com_Printf( "%i:steep\n", c_pmove );
+
+ // FIXME: if they can't slide down the slope, let them
+ // walk (sharp crevices)
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qtrue;
+ pml.walking = qfalse;
+ return;
+ }
+
+ pml.groundPlane = qtrue;
+ pml.walking = qtrue;
+
+ // hitting solid ground will end a waterjump
+ if( pm->ps->pm_flags & PMF_TIME_WATERJUMP )
+ {
+ pm->ps->pm_flags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND );
+ pm->ps->pm_time = 0;
+ }
+
+ if( pm->ps->groundEntityNum == ENTITYNUM_NONE )
+ {
+ // just hit the ground
+ if( pm->debugLevel )
+ Com_Printf( "%i:Land\n", c_pmove );
+
+ if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) )
+ PM_CrashLand( );
+
+ // don't do landing time if we were just going down a slope
+ if( pml.previous_velocity[ 2 ] < -200 )
+ {
+ // don't allow another jump for a little while
+ pm->ps->pm_flags |= PMF_TIME_LAND;
+ pm->ps->pm_time = 250;
+ }
+ }
+
+ pm->ps->groundEntityNum = trace.entityNum;
+
+ // don't reset the z velocity for slopes
+// pm->ps->velocity[2] = 0;
+
+ PM_AddTouchEnt( trace.entityNum );
+}
+
+
+/*
+=============
+PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving
+=============
+*/
+static void PM_SetWaterLevel( void )
+{
+ vec3_t point;
+ int cont;
+ int sample1;
+ int sample2;
+
+ //
+ // get waterlevel, accounting for ducking
+ //
+ pm->waterlevel = 0;
+ pm->watertype = 0;
+
+ point[ 0 ] = pm->ps->origin[ 0 ];
+ point[ 1 ] = pm->ps->origin[ 1 ];
+ point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + 1;
+ cont = pm->pointcontents( point, pm->ps->clientNum );
+
+ if( cont & MASK_WATER )
+ {
+ sample2 = pm->ps->viewheight - MINS_Z;
+ sample1 = sample2 / 2;
+
+ pm->watertype = cont;
+ pm->waterlevel = 1;
+ point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample1;
+ cont = pm->pointcontents( point, pm->ps->clientNum );
+
+ if( cont & MASK_WATER )
+ {
+ pm->waterlevel = 2;
+ point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample2;
+ cont = pm->pointcontents( point, pm->ps->clientNum );
+
+ if( cont & MASK_WATER )
+ pm->waterlevel = 3;
+ }
+ }
+}
+
+
+
+/*
+==============
+PM_CheckDuck
+
+Sets mins, maxs, and pm->ps->viewheight
+==============
+*/
+static void PM_CheckDuck (void)
+{
+ trace_t trace;
+ vec3_t PCmins, PCmaxs, PCcmaxs;
+ int PCvh, PCcvh;
+
+ BG_FindBBoxForClass( pm->ps->stats[ STAT_PCLASS ], PCmins, PCmaxs, PCcmaxs, NULL, NULL );
+ BG_FindViewheightForClass( pm->ps->stats[ STAT_PCLASS ], &PCvh, &PCcvh );
+
+ //TA: iD bug? you can still crouch when you're a spectator
+ if( pm->ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ PCcvh = PCvh;
+
+ pm->mins[ 0 ] = PCmins[ 0 ];
+ pm->mins[ 1 ] = PCmins[ 1 ];
+
+ pm->maxs[ 0 ] = PCmaxs[ 0 ];
+ pm->maxs[ 1 ] = PCmaxs[ 1 ];
+
+ pm->mins[ 2 ] = PCmins[ 2 ];
+
+ if( pm->ps->pm_type == PM_DEAD )
+ {
+ pm->maxs[ 2 ] = -8;
+ pm->ps->viewheight = DEAD_VIEWHEIGHT;
+ return;
+ }
+
+ //TA: If the standing and crouching viewheights are the same the class can't crouch
+ if( ( pm->cmd.upmove < 0 ) && ( PCvh != PCcvh ) &&
+ pm->ps->pm_type != PM_JETPACK &&
+ !BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) )
+ {
+ // duck
+ pm->ps->pm_flags |= PMF_DUCKED;
+ }
+ else
+ {
+ // stand up if possible
+ if( pm->ps->pm_flags & PMF_DUCKED )
+ {
+ // try to stand up
+ pm->maxs[ 2 ] = PCmaxs[ 2 ];
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
+ if( !trace.allsolid )
+ pm->ps->pm_flags &= ~PMF_DUCKED;
+ }
+ }
+
+ if( pm->ps->pm_flags & PMF_DUCKED )
+ {
+ pm->maxs[ 2 ] = PCcmaxs[ 2 ];
+ pm->ps->viewheight = PCcvh;
+ }
+ else
+ {
+ pm->maxs[ 2 ] = PCmaxs[ 2 ];
+ pm->ps->viewheight = PCvh;
+ }
+}
+
+
+
+//===================================================================
+
+
+/*
+===============
+PM_Footsteps
+===============
+*/
+static void PM_Footsteps( void )
+{
+ float bobmove;
+ int old;
+ qboolean footstep;
+
+ //
+ // calculate speed and cycle to be used for
+ // all cyclic walking effects
+ //
+ if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) && ( pml.groundPlane ) )
+ {
+ //TA: FIXME: yes yes i know this is wrong
+ pm->xyspeed = sqrt( pm->ps->velocity[ 0 ] * pm->ps->velocity[ 0 ]
+ + pm->ps->velocity[ 1 ] * pm->ps->velocity[ 1 ]
+ + pm->ps->velocity[ 2 ] * pm->ps->velocity[ 2 ] );
+ }
+ else
+ pm->xyspeed = sqrt( pm->ps->velocity[ 0 ] * pm->ps->velocity[ 0 ]
+ + pm->ps->velocity[ 1 ] * pm->ps->velocity[ 1 ] );
+
+ if( pm->ps->groundEntityNum == ENTITYNUM_NONE )
+ {
+ // airborne leaves position in cycle intact, but doesn't advance
+ if( pm->waterlevel > 1 )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_SWIM );
+ else
+ PM_ContinueLegsAnim( NSPA_SWIM );
+ }
+
+ return;
+ }
+
+ // if not trying to move
+ if( !pm->cmd.forwardmove && !pm->cmd.rightmove )
+ {
+ if( pm->xyspeed < 5 )
+ {
+ pm->ps->bobCycle = 0; // start at beginning of cycle again
+ if( pm->ps->pm_flags & PMF_DUCKED )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_IDLECR );
+ else
+ PM_ContinueLegsAnim( NSPA_STAND );
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_IDLE );
+ else
+ PM_ContinueLegsAnim( NSPA_STAND );
+ }
+ }
+ return;
+ }
+
+
+ footstep = qfalse;
+
+ if( pm->ps->pm_flags & PMF_DUCKED )
+ {
+ bobmove = 0.5; // ducked characters bob much faster
+
+ if( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_BACKCR );
+ else
+ {
+ if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKRIGHT );
+ else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKLEFT );
+ else
+ PM_ContinueLegsAnim( NSPA_WALKBACK );
+ }
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_WALKCR );
+ else
+ {
+ if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKRIGHT );
+ else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKLEFT );
+ else
+ PM_ContinueLegsAnim( NSPA_WALK );
+ }
+ }
+
+ // ducked characters never play footsteps
+ }
+ else
+ {
+ if( !( pm->cmd.buttons & BUTTON_WALKING ) )
+ {
+ bobmove = 0.4f; // faster speeds bob faster
+
+ if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE )
+ PM_ContinueLegsAnim( NSPA_CHARGE );
+ else if( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_BACK );
+ else
+ {
+ if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_RUNRIGHT );
+ else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_RUNLEFT );
+ else
+ PM_ContinueLegsAnim( NSPA_RUNBACK );
+ }
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_RUN );
+ else
+ {
+ if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_RUNRIGHT );
+ else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_RUNLEFT );
+ else
+ PM_ContinueLegsAnim( NSPA_RUN );
+ }
+ }
+
+ footstep = qtrue;
+ }
+ else
+ {
+ bobmove = 0.3f; // walking bobs slow
+ if( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_BACKWALK );
+ else
+ {
+ if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKRIGHT );
+ else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKLEFT );
+ else
+ PM_ContinueLegsAnim( NSPA_WALKBACK );
+ }
+ }
+ else
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_ContinueLegsAnim( LEGS_WALK );
+ else
+ {
+ if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKRIGHT );
+ else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove )
+ PM_ContinueLegsAnim( NSPA_WALKLEFT );
+ else
+ PM_ContinueLegsAnim( NSPA_WALK );
+ }
+ }
+ }
+ }
+
+ bobmove *= BG_FindBobCycleForClass( pm->ps->stats[ STAT_PCLASS ] );
+
+ if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST )
+ bobmove *= HUMAN_SPRINT_MODIFIER;
+
+ // check for footstep / splash sounds
+ old = pm->ps->bobCycle;
+ pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255;
+
+ // if we just crossed a cycle boundary, play an apropriate footstep event
+ if( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 )
+ {
+ if( pm->waterlevel == 0 )
+ {
+ // on ground will only play sounds if running
+ if( footstep && !pm->noFootsteps )
+ PM_AddEvent( PM_FootstepForSurface( ) );
+ }
+ else if( pm->waterlevel == 1 )
+ {
+ // splashing
+ PM_AddEvent( EV_FOOTSPLASH );
+ }
+ else if( pm->waterlevel == 2 )
+ {
+ // wading / swimming at surface
+ PM_AddEvent( EV_SWIM );
+ }
+ else if( pm->waterlevel == 3 )
+ {
+ // no sound when completely underwater
+ }
+ }
+}
+
+/*
+==============
+PM_WaterEvents
+
+Generate sound events for entering and leaving water
+==============
+*/
+static void PM_WaterEvents( void )
+{
+ // FIXME?
+ //
+ // if just entered a water volume, play a sound
+ //
+ if( !pml.previous_waterlevel && pm->waterlevel )
+ PM_AddEvent( EV_WATER_TOUCH );
+
+ //
+ // if just completely exited a water volume, play a sound
+ //
+ if( pml.previous_waterlevel && !pm->waterlevel )
+ PM_AddEvent( EV_WATER_LEAVE );
+
+ //
+ // check for head just going under water
+ //
+ if( pml.previous_waterlevel != 3 && pm->waterlevel == 3 )
+ PM_AddEvent( EV_WATER_UNDER );
+
+ //
+ // check for head just coming out of water
+ //
+ if( pml.previous_waterlevel == 3 && pm->waterlevel != 3 )
+ PM_AddEvent( EV_WATER_CLEAR );
+}
+
+
+/*
+===============
+PM_BeginWeaponChange
+===============
+*/
+static void PM_BeginWeaponChange( int weapon )
+{
+ if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS )
+ return;
+
+ if( !BG_InventoryContainsWeapon( weapon, pm->ps->stats ) )
+ return;
+
+ if( pm->ps->weaponstate == WEAPON_DROPPING )
+ return;
+
+ //special case to prevent storing a charged up lcannon
+ if( pm->ps->weapon == WP_LUCIFER_CANNON )
+ pm->ps->stats[ STAT_MISC ] = 0;
+
+ // cancel a reload
+ pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD;
+ if( pm->ps->weaponstate == WEAPON_RELOADING )
+ pm->ps->weaponTime = 0;
+
+ // force this here to prevent flamer effect from continuing, among other issues
+ pm->ps->generic1 = WPM_NOTFIRING;
+
+ PM_AddEvent( EV_CHANGE_WEAPON );
+ pm->ps->weaponstate = WEAPON_DROPPING;
+ pm->ps->weaponTime += 200;
+ pm->ps->persistant[ PERS_NEWWEAPON ] = weapon;
+
+ //reset build weapon
+ pm->ps->stats[ STAT_BUILDABLE ] = BA_NONE;
+
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_StartTorsoAnim( TORSO_DROP );
+}
+
+
+/*
+===============
+PM_FinishWeaponChange
+===============
+*/
+static void PM_FinishWeaponChange( void )
+{
+ int weapon;
+
+ weapon = pm->ps->persistant[ PERS_NEWWEAPON ];
+ if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS )
+ weapon = WP_NONE;
+
+ if( !BG_InventoryContainsWeapon( weapon, pm->ps->stats ) )
+ weapon = WP_NONE;
+
+ pm->ps->weapon = weapon;
+ pm->ps->weaponstate = WEAPON_RAISING;
+ pm->ps->weaponTime += 250;
+
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ PM_StartTorsoAnim( TORSO_RAISE );
+}
+
+
+/*
+==============
+PM_TorsoAnimation
+
+==============
+*/
+static void PM_TorsoAnimation( void )
+{
+ if( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL )
+ return;
+
+ if( pm->ps->weaponstate == WEAPON_READY )
+ {
+ if( pm->ps->weapon == WP_BLASTER )
+ PM_ContinueTorsoAnim( TORSO_STAND2 );
+ else
+ PM_ContinueTorsoAnim( TORSO_STAND );
+ }
+}
+
+
+/*
+==============
+PM_Weapon
+
+Generates weapon events and modifes the weapon counter
+==============
+*/
+static void PM_Weapon( void )
+{
+ int addTime = 200; //default addTime - should never be used
+ int ammo, clips, maxClips;
+ qboolean attack1 = qfalse;
+ qboolean attack2 = qfalse;
+ qboolean attack3 = qfalse;
+
+ // don't allow attack until all buttons are up
+ if( pm->ps->pm_flags & PMF_RESPAWNED )
+ return;
+
+ // ignore if spectator
+ if( pm->ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ return;
+
+ if( pm->ps->stats[ STAT_STATE ] & SS_INFESTING )
+ return;
+
+ if( pm->ps->stats[ STAT_STATE ] & SS_HOVELING )
+ return;
+
+ // check for dead player
+ if( pm->ps->stats[ STAT_HEALTH ] <= 0 )
+ {
+ pm->ps->weapon = WP_NONE;
+ return;
+ }
+
+
+ // no bite during pounce
+ if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG )
+ && ( pm->cmd.buttons & BUTTON_ATTACK )
+ && ( pm->ps->pm_flags & PMF_CHARGE ) )
+ {
+ return;
+ }
+
+ if( pm->ps->weaponTime > 0 )
+ pm->ps->weaponTime -= pml.msec;
+
+ // 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 )
+ {
+ //TA: must press use to switch weapons
+ if( pm->cmd.buttons & BUTTON_USE_HOLDABLE )
+ {
+ if( !( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) )
+ {
+ if( pm->cmd.weapon <= 32 )
+ {
+ //if trying to select a weapon, select it
+ if( pm->ps->weapon != pm->cmd.weapon )
+ PM_BeginWeaponChange( pm->cmd.weapon );
+ }
+ else if( pm->cmd.weapon > 32 )
+ {
+ //if trying to toggle an upgrade, toggle it
+ if( BG_InventoryContainsUpgrade( pm->cmd.weapon - 32, pm->ps->stats ) ) //sanity check
+ {
+ if( BG_UpgradeIsActive( pm->cmd.weapon - 32, pm->ps->stats ) )
+ BG_DeactivateUpgrade( pm->cmd.weapon - 32, pm->ps->stats );
+ else
+ BG_ActivateUpgrade( pm->cmd.weapon - 32, pm->ps->stats );
+ }
+ }
+ pm->ps->pm_flags |= PMF_USE_ITEM_HELD;
+ }
+ }
+ else
+ pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD;
+
+ //something external thinks a weapon change is necessary
+ if( pm->ps->pm_flags & PMF_WEAPON_SWITCH )
+ {
+ pm->ps->pm_flags &= ~PMF_WEAPON_SWITCH;
+ PM_BeginWeaponChange( pm->ps->persistant[ PERS_NEWWEAPON ] );
+ }
+ }
+
+ if( pm->ps->weaponTime > 0 )
+ return;
+
+ // change weapon if time
+ if( pm->ps->weaponstate == WEAPON_DROPPING )
+ {
+ PM_FinishWeaponChange( );
+ return;
+ }
+
+ if( pm->ps->weaponstate == WEAPON_RAISING )
+ {
+ pm->ps->weaponstate = WEAPON_READY;
+
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ {
+ if( pm->ps->weapon == WP_BLASTER )
+ PM_ContinueTorsoAnim( TORSO_STAND2 );
+ else
+ PM_ContinueTorsoAnim( TORSO_STAND );
+ }
+
+ return;
+ }
+
+ // start the animation even if out of ammo
+ ammo = pm->ps->ammo;
+ clips = pm->ps->clips;
+ BG_FindAmmoForWeapon( pm->ps->weapon, NULL, &maxClips );
+
+ // check for out of ammo
+ if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) )
+ {
+ PM_AddEvent( EV_NOAMMO );
+ pm->ps->weaponTime += 200;
+
+ if( pm->ps->weaponstate == WEAPON_FIRING )
+ pm->ps->weaponstate = WEAPON_READY;
+
+ return;
+ }
+
+ //done reloading so give em some ammo
+ if( pm->ps->weaponstate == WEAPON_RELOADING )
+ {
+ if( maxClips > 0 )
+ {
+ clips--;
+ BG_FindAmmoForWeapon( pm->ps->weapon, &ammo, NULL );
+ }
+
+ if( BG_FindUsesEnergyForWeapon( pm->ps->weapon ) &&
+ BG_InventoryContainsUpgrade( UP_BATTPACK, pm->ps->stats ) )
+ ammo = (int)( (float)ammo * BATTPACK_MODIFIER );
+
+ pm->ps->ammo = ammo;
+ pm->ps->clips = clips;
+
+ //allow some time for the weapon to be raised
+ pm->ps->weaponstate = WEAPON_RAISING;
+ PM_StartTorsoAnim( TORSO_RAISE );
+ pm->ps->weaponTime += 250;
+ return;
+ }
+
+ // check for end of clip
+ if( !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) &&
+ ( !ammo || pm->ps->pm_flags & PMF_WEAPON_RELOAD ) && clips )
+ {
+ pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD;
+
+ pm->ps->weaponstate = WEAPON_RELOADING;
+
+ //drop the weapon
+ PM_StartTorsoAnim( TORSO_DROP );
+
+ addTime = BG_FindReloadTimeForWeapon( pm->ps->weapon );
+
+ pm->ps->weaponTime += addTime;
+ return;
+ }
+
+ //check if non-auto primary/secondary attacks are permited
+ switch( pm->ps->weapon )
+ {
+ case WP_ALEVEL0:
+ //venom is only autohit
+ attack1 = attack2 = attack3 = qfalse;
+
+ if( !pm->autoWeaponHit[ pm->ps->weapon ] )
+ {
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ return;
+ }
+ break;
+
+ case WP_ALEVEL3:
+ case WP_ALEVEL3_UPG:
+ //pouncing has primary secondary AND autohit procedures
+ attack1 = pm->cmd.buttons & BUTTON_ATTACK;
+ attack2 = pm->cmd.buttons & BUTTON_ATTACK2;
+ attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE;
+
+ if( !pm->autoWeaponHit[ pm->ps->weapon ] && !attack1 && !attack2 && !attack3 )
+ {
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ return;
+ }
+ break;
+
+ case WP_LUCIFER_CANNON:
+ attack1 = pm->cmd.buttons & BUTTON_ATTACK;
+ attack2 = pm->cmd.buttons & BUTTON_ATTACK2;
+ attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE;
+
+ if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 && !attack3 )
+ {
+ if( pm->ps->stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE )
+ {
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ return;
+ }
+ else
+ attack1 = !attack1;
+ }
+
+ //erp this looks confusing
+ if( pm->ps->stats[ STAT_MISC ] > LCANNON_MIN_CHARGE )
+ attack1 = !attack1;
+ else if( pm->ps->stats[ STAT_MISC ] > 0 )
+ {
+ pm->ps->stats[ STAT_MISC ] = 0;
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ return;
+ }
+ break;
+
+ case WP_MASS_DRIVER:
+ attack1 = pm->cmd.buttons & BUTTON_ATTACK;
+ // attack2 is handled on the client for zooming (cg_view.c)
+
+ if( !attack1 )
+ {
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ return;
+ }
+ break;
+
+ default:
+ //by default primary and secondary attacks are allowed
+ attack1 = pm->cmd.buttons & BUTTON_ATTACK;
+ attack2 = pm->cmd.buttons & BUTTON_ATTACK2;
+ attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE;
+
+ if( !attack1 && !attack2 && !attack3 )
+ {
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ return;
+ }
+ break;
+ }
+
+ if ( pm->ps->weapon == WP_LUCIFER_CANNON && pm->ps->stats[ STAT_MISC ] > 0 && attack3 )
+ {
+ attack1 = qtrue;
+ attack3 = qfalse;
+ }
+
+ //TA: fire events for non auto weapons
+ if( attack3 )
+ {
+ if( BG_WeaponHasThirdMode( pm->ps->weapon ) )
+ {
+ //hacky special case for slowblob
+ if( pm->ps->weapon == WP_ALEVEL3_UPG && !ammo )
+ {
+ PM_AddEvent( EV_NOAMMO );
+ pm->ps->weaponTime += 200;
+ return;
+ }
+
+ pm->ps->generic1 = WPM_TERTIARY;
+ PM_AddEvent( EV_FIRE_WEAPON3 );
+ addTime = BG_FindRepeatRate3ForWeapon( pm->ps->weapon );
+ }
+ else
+ {
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ pm->ps->generic1 = WPM_NOTFIRING;
+ return;
+ }
+ }
+ else if( attack2 )
+ {
+ if( BG_WeaponHasAltMode( pm->ps->weapon ) )
+ {
+ pm->ps->generic1 = WPM_SECONDARY;
+ PM_AddEvent( EV_FIRE_WEAPON2 );
+ addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon );
+ }
+ else
+ {
+ pm->ps->weaponTime = 0;
+ pm->ps->weaponstate = WEAPON_READY;
+ pm->ps->generic1 = WPM_NOTFIRING;
+ return;
+ }
+ }
+ else if( attack1 )
+ {
+ pm->ps->generic1 = WPM_PRIMARY;
+ PM_AddEvent( EV_FIRE_WEAPON );
+ addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon );
+ }
+
+ //TA: fire events for autohit weapons
+ if( pm->autoWeaponHit[ pm->ps->weapon ] )
+ {
+ switch( pm->ps->weapon )
+ {
+ case WP_ALEVEL0:
+ pm->ps->generic1 = WPM_PRIMARY;
+ PM_AddEvent( EV_FIRE_WEAPON );
+ addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon );
+ break;
+
+ case WP_ALEVEL3:
+ case WP_ALEVEL3_UPG:
+ pm->ps->generic1 = WPM_SECONDARY;
+ PM_AddEvent( EV_FIRE_WEAPON2 );
+ addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ {
+ //FIXME: this should be an option in the client weapon.cfg
+ switch( pm->ps->weapon )
+ {
+ case WP_FLAMER:
+ if( pm->ps->weaponstate == WEAPON_READY )
+ {
+ PM_StartTorsoAnim( TORSO_ATTACK );
+ }
+ break;
+
+ case WP_BLASTER:
+ PM_StartTorsoAnim( TORSO_ATTACK2 );
+ break;
+
+ default:
+ PM_StartTorsoAnim( TORSO_ATTACK );
+ break;
+ }
+ }
+ else
+ {
+ if( pm->ps->weapon == WP_ALEVEL4 )
+ {
+ //hack to get random attack animations
+ //FIXME: does pm->ps->weaponTime cycle enough?
+ int num = abs( pm->ps->weaponTime ) % 3;
+
+ if( num == 0 )
+ PM_ForceLegsAnim( NSPA_ATTACK1 );
+ else if( num == 1 )
+ PM_ForceLegsAnim( NSPA_ATTACK2 );
+ else if( num == 2 )
+ PM_ForceLegsAnim( NSPA_ATTACK3 );
+ }
+ else
+ {
+ if( attack1 )
+ PM_ForceLegsAnim( NSPA_ATTACK1 );
+ else if( attack2 )
+ PM_ForceLegsAnim( NSPA_ATTACK2 );
+ else if( attack3 )
+ PM_ForceLegsAnim( NSPA_ATTACK3 );
+ }
+
+ pm->ps->torsoTimer = TIMER_ATTACK;
+ }
+
+ pm->ps->weaponstate = WEAPON_FIRING;
+
+ // take an ammo away if not infinite
+ if( !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) )
+ {
+ //special case for lCanon
+ if( pm->ps->weapon == WP_LUCIFER_CANNON && attack1 && !attack2 )
+ {
+ ammo -= (int)( ceil( ( (float)pm->ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE ) * 10.0f ) );
+
+ //stay on the safe side
+ if( ammo < 0 )
+ ammo = 0;
+ }
+ else
+ ammo--;
+
+ pm->ps->ammo = ammo;
+ pm->ps->clips = clips;
+ }
+ else if( pm->ps->weapon == WP_ALEVEL3_UPG && attack3 )
+ {
+ //special case for slowblob
+ ammo--;
+ pm->ps->ammo = ammo;
+ pm->ps->clips = clips;
+ }
+
+ //FIXME: predicted angles miss a problem??
+ if( pm->ps->weapon == WP_CHAINGUN )
+ {
+ if( pm->ps->pm_flags & PMF_DUCKED ||
+ BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) )
+ {
+ pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( ( ( random() * 0.5 ) - 0.125 ) * ( 30 / (float)addTime ) );
+ pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( ( ( random() * 0.5 ) - 0.25 ) * ( 30.0 / (float)addTime ) );
+ }
+ else
+ {
+ pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( ( ( random() * 8 ) - 2 ) * ( 30.0 / (float)addTime ) );
+ pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( ( ( random() * 8 ) - 4 ) * ( 30.0 / (float)addTime ) );
+ }
+ }
+
+ pm->ps->weaponTime += addTime;
+}
+
+/*
+================
+PM_Animate
+================
+*/
+static void PM_Animate( void )
+{
+ if( pm->cmd.buttons & BUTTON_GESTURE )
+ {
+ if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ {
+ if( pm->ps->torsoTimer == 0 )
+ {
+ PM_StartTorsoAnim( TORSO_GESTURE );
+ pm->ps->torsoTimer = TIMER_GESTURE;
+
+ PM_AddEvent( EV_TAUNT );
+ }
+ }
+ else
+ {
+ if( pm->ps->torsoTimer == 0 )
+ {
+ PM_ForceLegsAnim( NSPA_GESTURE );
+ pm->ps->torsoTimer = TIMER_GESTURE;
+
+ PM_AddEvent( EV_TAUNT );
+ }
+ }
+ }
+}
+
+
+/*
+================
+PM_DropTimers
+================
+*/
+static void PM_DropTimers( void )
+{
+ // drop misc timing counter
+ if( pm->ps->pm_time )
+ {
+ if( pml.msec >= pm->ps->pm_time )
+ {
+ pm->ps->pm_flags &= ~PMF_ALL_TIMES;
+ pm->ps->pm_time = 0;
+ }
+ else
+ pm->ps->pm_time -= pml.msec;
+ }
+
+ // drop animation counter
+ if( pm->ps->legsTimer > 0 )
+ {
+ pm->ps->legsTimer -= pml.msec;
+
+ if( pm->ps->legsTimer < 0 )
+ pm->ps->legsTimer = 0;
+ }
+
+ if( pm->ps->torsoTimer > 0 )
+ {
+ pm->ps->torsoTimer -= pml.msec;
+
+ if( pm->ps->torsoTimer < 0 )
+ pm->ps->torsoTimer = 0;
+ }
+}
+
+
+/*
+================
+PM_UpdateViewAngles
+
+This can be used as another entry point when only the viewangles
+are being updated instead of a full move
+================
+*/
+void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd )
+{
+ short temp[ 3 ];
+ int i;
+ vec3_t axis[ 3 ], rotaxis[ 3 ];
+ vec3_t tempang;
+
+ if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION )
+ return; // no view changes at all
+
+ if( ps->pm_type != PM_SPECTATOR && ps->stats[ STAT_HEALTH ] <= 0 )
+ return; // no view changes at all
+
+ // circularly clamp the angles with deltas
+ for( i = 0; i < 3; i++ )
+ {
+ temp[ i ] = cmd->angles[ i ] + ps->delta_angles[ i ];
+
+ if( i == PITCH )
+ {
+ // don't let the player look up or down more than 90 degrees
+ if( temp[ i ] > 16000 )
+ {
+ ps->delta_angles[ i ] = 16000 - cmd->angles[ i ];
+ temp[ i ] = 16000;
+ }
+ else if( temp[ i ] < -16000 )
+ {
+ ps->delta_angles[ i ] = -16000 - cmd->angles[ i ];
+ temp[ i ] = -16000;
+ }
+ }
+ tempang[ i ] = SHORT2ANGLE( temp[ i ] );
+ }
+
+ //convert viewangles -> axis
+ AnglesToAxis( tempang, axis );
+
+ if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ||
+ !BG_RotateAxis( ps->grapplePoint, axis, rotaxis, qfalse,
+ ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) )
+ AxisCopy( axis, rotaxis );
+
+ //convert the new axis back to angles
+ AxisToAngles( rotaxis, tempang );
+
+ //force angles to -180 <= x <= 180
+ for( i = 0; i < 3; i++ )
+ {
+ while( tempang[ i ] > 180.0f )
+ tempang[ i ] -= 360.0f;
+
+ while( tempang[ i ] < 180.0f )
+ tempang[ i ] += 360.0f;
+ }
+
+ //actually set the viewangles
+ for( i = 0; i < 3; i++ )
+ ps->viewangles[ i ] = tempang[ i ];
+
+ //pull the view into the lock point
+ if( ps->pm_type == PM_GRABBED && !BG_InventoryContainsUpgrade( UP_BATTLESUIT, ps->stats ) )
+ {
+ vec3_t dir, angles;
+
+ ByteToDir( ps->stats[ STAT_VIEWLOCK ], dir );
+ vectoangles( dir, angles );
+
+ for( i = 0; i < 3; i++ )
+ {
+ float diff = AngleSubtract( ps->viewangles[ i ], angles[ i ] );
+
+ while( diff > 180.0f )
+ diff -= 360.0f;
+ while( diff < -180.0f )
+ diff += 360.0f;
+
+ if( diff < -90.0f )
+ ps->delta_angles[ i ] += ANGLE2SHORT( fabs( diff ) - 90.0f );
+ else if( diff > 90.0f )
+ ps->delta_angles[ i ] -= ANGLE2SHORT( fabs( diff ) - 90.0f );
+
+ if( diff < 0.0f )
+ ps->delta_angles[ i ] += ANGLE2SHORT( fabs( diff ) * 0.05f );
+ else if( diff > 0.0f )
+ ps->delta_angles[ i ] -= ANGLE2SHORT( fabs( diff ) * 0.05f );
+ }
+ }
+}
+
+
+/*
+================
+PmoveSingle
+
+================
+*/
+void trap_SnapVector( float *v );
+
+void PmoveSingle( pmove_t *pmove )
+{
+ int ammo, clips;
+
+ pm = pmove;
+
+ ammo = pm->ps->ammo;
+ clips = pm->ps->clips;
+
+ // this counter lets us debug movement problems with a journal
+ // by setting a conditional breakpoint fot the previous frame
+ c_pmove++;
+
+ // clear results
+ pm->numtouch = 0;
+ pm->watertype = 0;
+ pm->waterlevel = 0;
+
+ if( pm->ps->stats[ STAT_HEALTH ] <= 0 )
+ pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies
+
+ // make sure walking button is clear if they are running, to avoid
+ // proxy no-footsteps cheats
+ if( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 )
+ pm->cmd.buttons &= ~BUTTON_WALKING;
+
+ // set the talk balloon flag
+ if( pm->cmd.buttons & BUTTON_TALK )
+ pm->ps->eFlags |= EF_TALK;
+ else
+ pm->ps->eFlags &= ~EF_TALK;
+
+ // 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 ) &&
+ ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) )
+ pm->ps->eFlags |= EF_FIRING;
+ else
+ pm->ps->eFlags &= ~EF_FIRING;
+
+ // 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_ATTACK2 ) &&
+ ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) )
+ pm->ps->eFlags |= EF_FIRING2;
+ else
+ pm->ps->eFlags &= ~EF_FIRING2;
+
+ // 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_USE_HOLDABLE ) &&
+ ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) )
+ pm->ps->eFlags |= EF_FIRING3;
+ else
+ pm->ps->eFlags &= ~EF_FIRING3;
+
+
+ // clear the respawned flag if attack and use are cleared
+ if( pm->ps->stats[STAT_HEALTH] > 0 &&
+ !( pm->cmd.buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) )
+ pm->ps->pm_flags &= ~PMF_RESPAWNED;
+
+ // if talk button is down, dissallow all other input
+ // this is to prevent any possible intercept proxy from
+ // adding fake talk balloons
+ if( pmove->cmd.buttons & BUTTON_TALK )
+ {
+ pmove->cmd.buttons = BUTTON_TALK;
+ pmove->cmd.forwardmove = 0;
+ pmove->cmd.rightmove = 0;
+ pmove->cmd.upmove = 0;
+ }
+
+ // clear all pmove local vars
+ memset( &pml, 0, sizeof( pml ) );
+
+ // determine the time
+ pml.msec = pmove->cmd.serverTime - pm->ps->commandTime;
+
+ if( pml.msec < 1 )
+ pml.msec = 1;
+ else if( pml.msec > 200 )
+ pml.msec = 200;
+
+ pm->ps->commandTime = pmove->cmd.serverTime;
+
+ // save old org in case we get stuck
+ VectorCopy( pm->ps->origin, pml.previous_origin );
+
+ // save old velocity for crashlanding
+ VectorCopy( pm->ps->velocity, pml.previous_velocity );
+
+ pml.frametime = pml.msec * 0.001;
+
+ AngleVectors( pm->ps->viewangles, pml.forward, pml.right, pml.up );
+
+ if( pm->cmd.upmove < 10 )
+ {
+ // not holding jump
+ pm->ps->pm_flags &= ~PMF_JUMP_HELD;
+ }
+
+ // decide if backpedaling animations should be used
+ if( pm->cmd.forwardmove < 0 )
+ pm->ps->pm_flags |= PMF_BACKWARDS_RUN;
+ else if( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) )
+ pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN;
+
+ if( pm->ps->pm_type >= PM_DEAD )
+ {
+ pm->cmd.forwardmove = 0;
+ pm->cmd.rightmove = 0;
+ pm->cmd.upmove = 0;
+ }
+
+ if( pm->ps->pm_type == PM_SPECTATOR )
+ {
+ // update the viewangles
+ PM_UpdateViewAngles( pm->ps, &pm->cmd );
+ PM_CheckDuck( );
+ PM_FlyMove( );
+ PM_DropTimers( );
+ return;
+ }
+
+ if( pm->ps->pm_type == PM_NOCLIP )
+ {
+ PM_UpdateViewAngles( pm->ps, &pm->cmd );
+ PM_NoclipMove( );
+ PM_DropTimers( );
+ return;
+ }
+
+ if( pm->ps->pm_type == PM_FREEZE)
+ return; // no movement at all
+
+ if( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION )
+ return; // no movement at all
+
+ // set watertype, and waterlevel
+ PM_SetWaterLevel( );
+ pml.previous_waterlevel = pmove->waterlevel;
+
+ // set mins, maxs, and viewheight
+ PM_CheckDuck( );
+
+ PM_CheckLadder( );
+
+ // set groundentity
+ PM_GroundTrace( );
+
+ // update the viewangles
+ PM_UpdateViewAngles( pm->ps, &pm->cmd );
+
+ if( pm->ps->pm_type == PM_DEAD || pm->ps->pm_type == PM_GRABBED )
+ PM_DeadMove( );
+
+ PM_DropTimers( );
+
+ if( pm->ps->pm_type == PM_JETPACK )
+ PM_JetPackMove( );
+ else if( pm->ps->pm_flags & PMF_TIME_WATERJUMP )
+ PM_WaterJumpMove( );
+ else if( pm->waterlevel > 1 )
+ PM_WaterMove( );
+ else if( pml.ladder )
+ PM_LadderMove( );
+ else if( pml.walking )
+ {
+ if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) &&
+ ( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) )
+ PM_ClimbMove( ); //TA: walking on any surface
+ else
+ PM_WalkMove( ); // walking on ground
+ }
+ else
+ PM_AirMove( );
+
+ PM_Animate( );
+
+ // set groundentity, watertype, and waterlevel
+ PM_GroundTrace( );
+ //TA: must update after every GroundTrace() - yet more clock cycles down the drain :( (14 vec rotations/frame)
+ // update the viewangles
+ PM_UpdateViewAngles( pm->ps, &pm->cmd );
+
+ PM_SetWaterLevel( );
+
+ // weapons
+ PM_Weapon( );
+
+ // torso animation
+ PM_TorsoAnimation( );
+
+ // footstep events / legs animations
+ PM_Footsteps( );
+
+ // entering / leaving water splashes
+ PM_WaterEvents( );
+
+ // snap some parts of playerstate to save network bandwidth
+ trap_SnapVector( pm->ps->velocity );
+}
+
+
+/*
+================
+Pmove
+
+Can be called by either the server or the client
+================
+*/
+void Pmove( pmove_t *pmove )
+{
+ int finalTime;
+
+ finalTime = pmove->cmd.serverTime;
+
+ if( finalTime < pmove->ps->commandTime )
+ return; // should not happen
+
+ if( finalTime > pmove->ps->commandTime + 1000 )
+ pmove->ps->commandTime = finalTime - 1000;
+
+ pmove->ps->pmove_framecount = ( pmove->ps->pmove_framecount + 1 ) & ( ( 1 << PS_PMOVEFRAMECOUNTBITS ) - 1 );
+
+ // chop the move up if it is too long, to prevent framerate
+ // dependent behavior
+ while( pmove->ps->commandTime != finalTime )
+ {
+ int msec;
+
+ msec = finalTime - pmove->ps->commandTime;
+
+ if( pmove->pmove_fixed )
+ {
+ if( msec > pmove->pmove_msec )
+ msec = pmove->pmove_msec;
+ }
+ else
+ {
+ if( msec > 66 )
+ msec = 66;
+ }
+
+
+ pmove->cmd.serverTime = pmove->ps->commandTime + msec;
+ PmoveSingle( pmove );
+
+ if( pmove->ps->pm_flags & PMF_JUMP_HELD )
+ pmove->cmd.upmove = 20;
+ }
+}
diff --git a/src/game/bg_public.h b/src/game/bg_public.h
new file mode 100644
index 0000000..462bf95
--- /dev/null
+++ b/src/game/bg_public.h
@@ -0,0 +1,1330 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// bg_public.h -- definitions shared by both the server game and client game modules
+
+//tremulous balance header
+#include "tremulous.h"
+
+// because games can change separately from the main system version, we need a
+// second version that must match between game and cgame
+#define GAME_VERSION "base"
+
+#define DEFAULT_GRAVITY 800
+
+#define VOTE_TIME 30000 // 30 seconds before vote times out
+
+#define MINS_Z -24
+#define DEFAULT_VIEWHEIGHT 26
+#define CROUCH_VIEWHEIGHT 12
+#define DEAD_VIEWHEIGHT -14 //TA: watch for mins[ 2 ] less than this causing
+
+//
+// config strings are a general means of communicating variable length strings
+// from the server to all connected clients.
+//
+
+// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h
+#define CS_MUSIC 2
+#define CS_MESSAGE 3 // from the map worldspawn's message field
+#define CS_MOTD 4 // g_motd string for server message of the day
+#define CS_WARMUP 5 // server time when the match will be restarted
+// 6 UNUSED
+// 7 UNUSED
+#define CS_VOTE_TIME 8
+#define CS_VOTE_STRING 9
+#define CS_VOTE_YES 10
+#define CS_VOTE_NO 11
+
+#define CS_TEAMVOTE_TIME 12
+#define CS_TEAMVOTE_STRING 14
+#define CS_TEAMVOTE_YES 16
+#define CS_TEAMVOTE_NO 18
+
+#define CS_GAME_VERSION 20
+#define CS_LEVEL_START_TIME 21 // so the timer only shows the current level
+#define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two
+#define CS_WINNER 23 // string indicating round winner
+#define CS_SHADERSTATE 24
+#define CS_BOTINFO 25
+#define CS_CLIENTS_READY 26 //TA: following suggestion in STAT_ enum STAT_CLIENTS_READY becomes a configstring
+
+//TA: extra stuff:
+#define CS_BUILDPOINTS 28
+#define CS_STAGES 29
+#define CS_SPAWNS 30
+
+#define CS_MODELS 33
+#define CS_SOUNDS (CS_MODELS+MAX_MODELS)
+#define CS_SHADERS (CS_SOUNDS+MAX_SOUNDS)
+#define CS_PARTICLE_SYSTEMS (CS_SHADERS+MAX_GAME_SHADERS)
+#define CS_PLAYERS (CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS)
+#define CS_PRECACHES (CS_PLAYERS+MAX_CLIENTS)
+#define CS_LOCATIONS (CS_PRECACHES+MAX_CLIENTS)
+
+#define CS_MAX (CS_LOCATIONS+MAX_LOCATIONS)
+
+#if (CS_MAX) > MAX_CONFIGSTRINGS
+#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS
+#endif
+
+typedef enum
+{
+ GENDER_MALE,
+ GENDER_FEMALE,
+ GENDER_NEUTER
+} gender_t;
+
+/*
+===================================================================================
+
+PMOVE MODULE
+
+The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t
+and some other output data. Used for local prediction on the client game and true
+movement on the server game.
+===================================================================================
+*/
+
+typedef enum
+{
+ PM_NORMAL, // can accelerate and turn
+ PM_NOCLIP, // noclip movement
+ PM_SPECTATOR, // still run into walls
+ PM_JETPACK, // jetpack physics
+ PM_GRABBED, // like dead, but for when the player is still live
+ PM_DEAD, // no acceleration or turning, but free falling
+ PM_FREEZE, // stuck in place with no control
+ PM_INTERMISSION, // no movement or status bar
+ PM_SPINTERMISSION // no movement or status bar
+} pmtype_t;
+
+typedef enum
+{
+ WEAPON_READY,
+ WEAPON_RAISING,
+ WEAPON_DROPPING,
+ WEAPON_FIRING,
+ WEAPON_RELOADING
+} weaponstate_t;
+
+// pmove->pm_flags
+#define PMF_DUCKED 1
+#define PMF_JUMP_HELD 2
+#define PMF_CROUCH_HELD 4
+#define PMF_BACKWARDS_JUMP 8 // go into backwards land
+#define PMF_BACKWARDS_RUN 16 // coast down to backwards run
+#define PMF_TIME_LAND 32 // pm_time is time before rejump
+#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time
+#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump
+#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up
+#define PMF_USE_ITEM_HELD 1024
+#define PMF_WEAPON_RELOAD 2048 //TA: force a weapon switch
+#define PMF_FOLLOW 4096 // spectate following another player
+#define PMF_QUEUED 8192 //TA: player is queued
+#define PMF_TIME_WALLJUMP 16384 //TA: for limiting wall jumping
+#define PMF_CHARGE 32768 //TA: keep track of pouncing
+#define PMF_WEAPON_SWITCH 65536 //TA: force a weapon switch
+
+
+#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK|PMF_TIME_WALLJUMP)
+
+typedef struct
+{
+ int pouncePayload;
+} pmoveExt_t;
+
+#define MAXTOUCH 32
+typedef struct
+{
+ // state (in / out)
+ playerState_t *ps;
+ pmoveExt_t *pmext;
+ // command (in)
+ usercmd_t cmd;
+ int tracemask; // collide against these types of surfaces
+ int debugLevel; // if set, diagnostic output will be printed
+ qboolean noFootsteps; // if the game is setup for no footsteps by the server
+ qboolean autoWeaponHit[ 32 ]; //FIXME: TA: remind myself later this might be a problem
+
+ int framecount;
+
+ // results (out)
+ int numtouch;
+ int touchents[ MAXTOUCH ];
+
+ vec3_t mins, maxs; // bounding box size
+
+ int watertype;
+ int waterlevel;
+
+ float xyspeed;
+
+ // for fixed msec Pmove
+ int pmove_fixed;
+ int pmove_msec;
+
+ // callbacks to test the world
+ // these will be different functions during game and cgame
+ /*void (*trace)( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask );*/
+ void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs,
+ const vec3_t end, int passEntityNum, int contentMask );
+
+
+ int (*pointcontents)( const vec3_t point, int passEntityNum );
+} pmove_t;
+
+// if a full pmove isn't done on the client, you can just update the angles
+void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd );
+void Pmove( pmove_t *pmove );
+
+//===================================================================================
+
+
+// player_state->stats[] indexes
+typedef enum
+{
+ STAT_HEALTH,
+ STAT_ITEMS,
+ STAT_SLOTS, //TA: tracks the amount of stuff human players are carrying
+ STAT_ACTIVEITEMS,
+ STAT_WEAPONS, // 16 bit fields
+ STAT_WEAPONS2, //TA: another 16 bits to push the max weapon count up
+ STAT_MAX_HEALTH, // health / armor limit, changable by handicap
+ STAT_PCLASS, //TA: player class (for aliens AND humans)
+ STAT_PTEAM, //TA: player team
+ STAT_STAMINA, //TA: stamina (human only)
+ STAT_STATE, //TA: client states e.g. wall climbing
+ STAT_MISC, //TA: for uh...misc stuff
+ STAT_BUILDABLE, //TA: which ghost model to display for building
+ STAT_BOOSTTIME, //TA: time left for boost (alien only)
+ STAT_FALLDIST, //TA: the distance the player fell
+ STAT_VIEWLOCK //TA: direction to lock the view in
+} statIndex_t;
+
+#define SCA_WALLCLIMBER 0x00000001
+#define SCA_TAKESFALLDAMAGE 0x00000002
+#define SCA_CANZOOM 0x00000004
+#define SCA_NOWEAPONDRIFT 0x00000008
+#define SCA_FOVWARPS 0x00000010
+#define SCA_ALIENSENSE 0x00000020
+#define SCA_CANUSELADDERS 0x00000040
+#define SCA_WALLJUMPER 0x00000080
+
+#define SS_WALLCLIMBING 0x00000001
+#define SS_WALLCLIMBINGCEILING 0x00000002
+#define SS_CREEPSLOWED 0x00000004
+#define SS_SPEEDBOOST 0x00000008
+#define SS_INFESTING 0x00000010
+#define SS_GRABBED 0x00000020
+#define SS_BLOBLOCKED 0x00000040
+#define SS_POISONED 0x00000080
+#define SS_HOVELING 0x00000100
+#define SS_BOOSTED 0x00000200
+#define SS_SLOWLOCKED 0x00000400
+#define SS_POISONCLOUDED 0x00000800
+#define SS_MEDKIT_ACTIVE 0x00001000
+#define SS_CHARGING 0x00002000
+
+#define SB_VALID_TOGGLEBIT 0x00004000
+
+#define MAX_STAMINA 1000
+
+// player_state->persistant[] indexes
+// these fields are the only part of player_state that isn't
+// cleared on respawn
+typedef enum
+{
+ PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!!
+ PERS_HITS, // total points damage inflicted so damage beeps can sound on change
+ PERS_UNUSED, // used to be PERS_RANK, no longer used
+ PERS_TEAM,
+ PERS_SPAWN_COUNT, // incremented every respawn
+ PERS_ATTACKER, // clientnum of last damage inflicter
+ PERS_KILLED, // count of the number of times you died
+
+ //TA:
+ PERS_STATE,
+ PERS_CREDIT, // human credit
+ PERS_BANK, // human credit in the bank
+ PERS_QUEUEPOS, // position in the spawn queue
+ PERS_NEWWEAPON // weapon to switch to
+} persEnum_t;
+
+#define PS_WALLCLIMBINGFOLLOW 0x00000001
+#define PS_WALLCLIMBINGTOGGLE 0x00000002
+#define PS_NONSEGMODEL 0x00000004
+
+// entityState_t->eFlags
+#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD
+#define EF_TELEPORT_BIT 0x00000002 // toggled every time the origin abruptly changes
+#define EF_PLAYER_EVENT 0x00000004
+#define EF_BOUNCE 0x00000008 // for missiles
+#define EF_BOUNCE_HALF 0x00000010 // for missiles
+#define EF_NO_BOUNCE_SOUND 0x00000020 // for missiles
+#define EF_WALLCLIMB 0x00000040 // TA: wall walking
+#define EF_WALLCLIMBCEILING 0x00000080 // TA: wall walking ceiling hack
+#define EF_NODRAW 0x00000100 // may have an event, but no model (unspawned items)
+#define EF_FIRING 0x00000200 // for lightning gun
+#define EF_FIRING2 0x00000400 // alt fire
+#define EF_FIRING3 0x00000800 // third fire
+#define EF_MOVER_STOP 0x00001000 // will push otherwise
+#define EF_TALK 0x00002000 // draw a talk balloon
+#define EF_CONNECTION 0x00004000 // draw a connection trouble sprite
+#define EF_VOTED 0x00008000 // already cast a vote
+#define EF_TEAMVOTED 0x00010000 // already cast a vote
+#define EF_BLOBLOCKED 0x00020000 // TA: caught by a trapper
+#define EF_REAL_LIGHT 0x00040000 // TA: light sprites according to ambient light
+#define EF_DBUILDER 0x00080000 // designated builder protection
+
+typedef enum
+{
+ PW_NONE,
+
+ PW_QUAD,
+ PW_BATTLESUIT,
+ PW_HASTE,
+ PW_INVIS,
+ PW_REGEN,
+ PW_FLIGHT,
+
+ PW_REDFLAG,
+ PW_BLUEFLAG,
+ PW_BALL,
+
+ PW_NUM_POWERUPS
+} powerup_t;
+
+typedef enum
+{
+ HI_NONE,
+
+ HI_TELEPORTER,
+ HI_MEDKIT,
+
+ HI_NUM_HOLDABLE
+} holdable_t;
+
+typedef enum
+{
+ WPM_NONE,
+
+ WPM_PRIMARY,
+ WPM_SECONDARY,
+ WPM_TERTIARY,
+
+ WPM_NOTFIRING,
+
+ WPM_NUM_WEAPONMODES
+} weaponMode_t;
+
+typedef enum
+{
+ WP_NONE,
+
+ WP_ALEVEL0,
+ WP_ALEVEL1,
+ WP_ALEVEL1_UPG,
+ WP_ALEVEL2,
+ WP_ALEVEL2_UPG,
+ WP_ALEVEL3,
+ WP_ALEVEL3_UPG,
+ WP_ALEVEL4,
+
+ WP_BLASTER,
+ WP_MACHINEGUN,
+ WP_PAIN_SAW,
+ WP_SHOTGUN,
+ WP_LAS_GUN,
+ WP_MASS_DRIVER,
+ WP_CHAINGUN,
+ WP_PULSE_RIFLE,
+ WP_FLAMER,
+ WP_LUCIFER_CANNON,
+ WP_GRENADE,
+
+ WP_LOCKBLOB_LAUNCHER,
+ WP_HIVE,
+ WP_TESLAGEN,
+ WP_MGTURRET,
+
+ //build weapons must remain in a block
+ WP_ABUILD,
+ WP_ABUILD2,
+ WP_HBUILD2,
+ WP_HBUILD,
+ //ok?
+
+ WP_NUM_WEAPONS
+} weapon_t;
+
+typedef enum
+{
+ UP_NONE,
+
+ UP_LIGHTARMOUR,
+ UP_HELMET,
+ UP_MEDKIT,
+ UP_BATTPACK,
+ UP_JETPACK,
+ UP_BATTLESUIT,
+ UP_GRENADE,
+
+ UP_AMMO,
+
+ UP_NUM_UPGRADES
+} upgrade_t;
+
+typedef enum
+{
+ WUT_NONE,
+
+ WUT_ALIENS,
+ WUT_HUMANS,
+
+ WUT_NUM_TEAMS
+} WUTeam_t;
+
+//TA: bitmasks for upgrade slots
+#define SLOT_NONE 0x00000000
+#define SLOT_HEAD 0x00000001
+#define SLOT_TORSO 0x00000002
+#define SLOT_ARMS 0x00000004
+#define SLOT_LEGS 0x00000008
+#define SLOT_BACKPACK 0x00000010
+#define SLOT_WEAPON 0x00000020
+#define SLOT_SIDEARM 0x00000040
+
+typedef enum
+{
+ BA_NONE,
+
+ BA_A_SPAWN,
+ BA_A_OVERMIND,
+
+ BA_A_BARRICADE,
+ BA_A_ACIDTUBE,
+ BA_A_TRAPPER,
+ BA_A_BOOSTER,
+ BA_A_HIVE,
+
+ BA_A_HOVEL,
+
+ BA_H_SPAWN,
+
+ BA_H_MGTURRET,
+ BA_H_TESLAGEN,
+
+ BA_H_ARMOURY,
+ BA_H_DCC,
+ BA_H_MEDISTAT,
+
+ BA_H_REACTOR,
+ BA_H_REPEATER,
+
+ BA_NUM_BUILDABLES
+} buildable_t;
+
+typedef enum
+{
+ BIT_NONE,
+
+ BIT_ALIENS,
+ BIT_HUMANS,
+
+ BIT_NUM_TEAMS
+} buildableTeam_t;
+
+#define B_HEALTH_BITS 5
+#define B_HEALTH_MASK ((1<<B_HEALTH_BITS)-1)
+
+#define B_DCCED_TOGGLEBIT 0x00000000
+#define B_SPAWNED_TOGGLEBIT 0x00000020
+#define B_POWERED_TOGGLEBIT 0x00000040
+#define B_MARKED_TOGGLEBIT 0x00000080
+
+
+// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS])
+#define PLAYEREVENT_DENIEDREWARD 0x0001
+#define PLAYEREVENT_GAUNTLETREWARD 0x0002
+#define PLAYEREVENT_HOLYSHIT 0x0004
+
+// entityState_t->event values
+// entity events are for effects that take place reletive
+// to an existing entities origin. Very network efficient.
+
+// two bits at the top of the entityState->event field
+// will be incremented with each change in the event so
+// that an identical event started twice in a row can
+// be distinguished. And off the value with ~EV_EVENT_BITS
+// to retrieve the actual event number
+#define EV_EVENT_BIT1 0x00000100
+#define EV_EVENT_BIT2 0x00000200
+#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2)
+
+#define EVENT_VALID_MSEC 300
+
+typedef enum
+{
+ EV_NONE,
+
+ EV_FOOTSTEP,
+ EV_FOOTSTEP_METAL,
+ EV_FOOTSTEP_SQUELCH,
+ EV_FOOTSPLASH,
+ EV_FOOTWADE,
+ EV_SWIM,
+
+ EV_STEP_4,
+ EV_STEP_8,
+ EV_STEP_12,
+ EV_STEP_16,
+
+ EV_STEPDN_4,
+ EV_STEPDN_8,
+ EV_STEPDN_12,
+ EV_STEPDN_16,
+
+ EV_FALL_SHORT,
+ EV_FALL_MEDIUM,
+ EV_FALL_FAR,
+ EV_FALLING,
+
+ EV_JUMP,
+ EV_WATER_TOUCH, // foot touches
+ EV_WATER_LEAVE, // foot leaves
+ EV_WATER_UNDER, // head touches
+ EV_WATER_CLEAR, // head leaves
+
+ EV_NOAMMO,
+ EV_CHANGE_WEAPON,
+ EV_FIRE_WEAPON,
+ EV_FIRE_WEAPON2,
+ EV_FIRE_WEAPON3,
+
+ EV_PLAYER_RESPAWN, //TA: for fovwarp effects
+ EV_PLAYER_TELEPORT_IN,
+ EV_PLAYER_TELEPORT_OUT,
+
+ EV_GRENADE_BOUNCE, // eventParm will be the soundindex
+
+ EV_GENERAL_SOUND,
+ EV_GLOBAL_SOUND, // no attenuation
+
+ EV_BULLET_HIT_FLESH,
+ EV_BULLET_HIT_WALL,
+
+ EV_SHOTGUN,
+
+ EV_MISSILE_HIT,
+ EV_MISSILE_MISS,
+ EV_MISSILE_MISS_METAL,
+ EV_TESLATRAIL,
+ EV_BULLET, // otherEntity is the shooter
+
+ EV_LEV1_GRAB,
+ EV_LEV4_CHARGE_PREPARE,
+ EV_LEV4_CHARGE_START,
+
+ EV_PAIN,
+ EV_DEATH1,
+ EV_DEATH2,
+ EV_DEATH3,
+ EV_OBITUARY,
+
+ EV_GIB_PLAYER, // gib a previously living player
+
+ EV_BUILD_CONSTRUCT, //TA
+ EV_BUILD_DESTROY, //TA
+ EV_BUILD_DELAY, //TA: can't build yet
+ EV_BUILD_REPAIR, //TA: repairing buildable
+ EV_BUILD_REPAIRED, //TA: buildable has full health
+ EV_HUMAN_BUILDABLE_EXPLOSION,
+ EV_ALIEN_BUILDABLE_EXPLOSION,
+ EV_ALIEN_ACIDTUBE,
+
+ EV_MEDKIT_USED,
+
+ EV_ALIEN_EVOLVE,
+ EV_ALIEN_EVOLVE_FAILED,
+
+ EV_DEBUG_LINE,
+ EV_STOPLOOPINGSOUND,
+ EV_TAUNT,
+
+ EV_OVERMIND_ATTACK, //TA: overmind under attack
+ EV_OVERMIND_DYING, //TA: overmind close to death
+ EV_OVERMIND_SPAWNS, //TA: overmind needs spawns
+
+ EV_DCC_ATTACK, //TA: dcc under attack
+
+ EV_RPTUSE_SOUND //TA: trigger a sound
+} entity_event_t;
+
+typedef enum
+{
+ MN_TEAM,
+ MN_A_TEAMFULL,
+ MN_H_TEAMFULL,
+
+ //alien stuff
+ MN_A_CLASS,
+ MN_A_BUILD,
+ MN_A_INFEST,
+ MN_A_HOVEL_OCCUPIED,
+ MN_A_HOVEL_BLOCKED,
+ MN_A_NOEROOM,
+ MN_A_TOOCLOSE,
+ MN_A_NOOVMND_EVOLVE,
+
+ //alien build
+ MN_A_SPWNWARN,
+ MN_A_OVERMIND,
+ MN_A_NOASSERT,
+ MN_A_NOCREEP,
+ MN_A_NOOVMND,
+ MN_A_NOROOM,
+ MN_A_NORMAL,
+ MN_A_HOVEL,
+ MN_A_HOVEL_EXIT,
+
+ //human stuff
+ MN_H_SPAWN,
+ MN_H_BUILD,
+ MN_H_ARMOURY,
+ MN_H_NOSLOTS,
+ MN_H_NOFUNDS,
+ MN_H_ITEMHELD,
+
+ //human build
+ MN_H_REPEATER,
+ MN_H_NOPOWER,
+ MN_H_NOTPOWERED,
+ MN_H_NODCC,
+ MN_H_REACTOR,
+ MN_H_NOROOM,
+ MN_H_NORMAL,
+ MN_H_TNODEWARN,
+ MN_H_RPTWARN,
+ MN_H_RPTWARN2,
+
+ //not used
+ MN_A_TEAMCHANGEBUILDTIMER,
+ MN_H_TEAMCHANGEBUILDTIMER,
+
+ MN_A_EVOLVEBUILDTIMER,
+
+ MN_H_NOENERGYAMMOHERE,
+ MN_H_NOARMOURYHERE,
+ MN_H_NOROOMBSUITON,
+ MN_H_NOROOMBSUITOFF,
+ MN_H_ARMOURYBUILDTIMER
+} dynMenu_t;
+
+// animations
+typedef enum
+{
+ BOTH_DEATH1,
+ BOTH_DEAD1,
+ BOTH_DEATH2,
+ BOTH_DEAD2,
+ BOTH_DEATH3,
+ BOTH_DEAD3,
+
+ TORSO_GESTURE,
+
+ TORSO_ATTACK,
+ TORSO_ATTACK2,
+
+ TORSO_DROP,
+ TORSO_RAISE,
+
+ TORSO_STAND,
+ TORSO_STAND2,
+
+ LEGS_WALKCR,
+ LEGS_WALK,
+ LEGS_RUN,
+ LEGS_BACK,
+ LEGS_SWIM,
+
+ LEGS_JUMP,
+ LEGS_LAND,
+
+ LEGS_JUMPB,
+ LEGS_LANDB,
+
+ LEGS_IDLE,
+ LEGS_IDLECR,
+
+ LEGS_TURN,
+
+ TORSO_GETFLAG,
+ TORSO_GUARDBASE,
+ TORSO_PATROL,
+ TORSO_FOLLOWME,
+ TORSO_AFFIRMATIVE,
+ TORSO_NEGATIVE,
+
+ MAX_PLAYER_ANIMATIONS,
+
+ LEGS_BACKCR,
+ LEGS_BACKWALK,
+ FLAG_RUN,
+ FLAG_STAND,
+ FLAG_STAND2RUN,
+
+ MAX_PLAYER_TOTALANIMATIONS
+} playerAnimNumber_t;
+
+// nonsegmented animations
+typedef enum
+{
+ NSPA_STAND,
+
+ NSPA_GESTURE,
+
+ NSPA_WALK,
+ NSPA_RUN,
+ NSPA_RUNBACK,
+ NSPA_CHARGE,
+
+ NSPA_RUNLEFT,
+ NSPA_WALKLEFT,
+ NSPA_RUNRIGHT,
+ NSPA_WALKRIGHT,
+
+ NSPA_SWIM,
+
+ NSPA_JUMP,
+ NSPA_LAND,
+ NSPA_JUMPBACK,
+ NSPA_LANDBACK,
+
+ NSPA_TURN,
+
+ NSPA_ATTACK1,
+ NSPA_ATTACK2,
+ NSPA_ATTACK3,
+
+ NSPA_PAIN1,
+ NSPA_PAIN2,
+
+ NSPA_DEATH1,
+ NSPA_DEAD1,
+ NSPA_DEATH2,
+ NSPA_DEAD2,
+ NSPA_DEATH3,
+ NSPA_DEAD3,
+
+ MAX_NONSEG_PLAYER_ANIMATIONS,
+
+ NSPA_WALKBACK,
+
+ MAX_NONSEG_PLAYER_TOTALANIMATIONS
+} nonSegPlayerAnimNumber_t;
+
+//TA: for buildable animations
+typedef enum
+{
+ BANIM_NONE,
+
+ BANIM_CONSTRUCT1,
+ BANIM_CONSTRUCT2,
+
+ BANIM_IDLE1,
+ BANIM_IDLE2,
+ BANIM_IDLE3,
+
+ BANIM_ATTACK1,
+ BANIM_ATTACK2,
+
+ BANIM_SPAWN1,
+ BANIM_SPAWN2,
+
+ BANIM_PAIN1,
+ BANIM_PAIN2,
+
+ BANIM_DESTROY1,
+ BANIM_DESTROY2,
+ BANIM_DESTROYED,
+
+ MAX_BUILDABLE_ANIMATIONS
+} buildableAnimNumber_t;
+
+typedef struct animation_s
+{
+ int firstFrame;
+ int numFrames;
+ int loopFrames; // 0 to numFrames
+ int frameLerp; // msec between frames
+ int initialLerp; // msec to get to first frame
+ int reversed; // true if animation is reversed
+ int flipflop; // true if animation should flipflop back to base
+} animation_t;
+
+
+// flip the togglebit every time an animation
+// changes so a restart of the same anim can be detected
+#define ANIM_TOGGLEBIT 0x80
+#define ANIM_FORCEBIT 0x40
+
+
+typedef enum
+{
+ TEAM_FREE,
+ TEAM_SPECTATOR,
+
+ TEAM_NUM_TEAMS
+} team_t;
+
+// Time between location updates
+#define TEAM_LOCATION_UPDATE_TIME 1000
+
+// How many players on the overlay
+#define TEAM_MAXOVERLAY 32
+
+//TA: player classes
+typedef enum
+{
+ PCL_NONE,
+
+ //builder classes
+ PCL_ALIEN_BUILDER0,
+ PCL_ALIEN_BUILDER0_UPG,
+
+ //offensive classes
+ PCL_ALIEN_LEVEL0,
+ PCL_ALIEN_LEVEL1,
+ PCL_ALIEN_LEVEL1_UPG,
+ PCL_ALIEN_LEVEL2,
+ PCL_ALIEN_LEVEL2_UPG,
+ PCL_ALIEN_LEVEL3,
+ PCL_ALIEN_LEVEL3_UPG,
+ PCL_ALIEN_LEVEL4,
+
+ //human class
+ PCL_HUMAN,
+ PCL_HUMAN_BSUIT,
+
+ PCL_NUM_CLASSES
+} pClass_t;
+
+
+//TA: player teams
+typedef enum
+{
+ PTE_NONE,
+ PTE_ALIENS,
+ PTE_HUMANS,
+
+ PTE_NUM_TEAMS
+} pTeam_t;
+
+
+// means of death
+typedef enum
+{
+ MOD_UNKNOWN,
+ MOD_SHOTGUN,
+ MOD_BLASTER,
+ MOD_PAINSAW,
+ MOD_MACHINEGUN,
+ MOD_CHAINGUN,
+ MOD_PRIFLE,
+ MOD_MDRIVER,
+ MOD_LASGUN,
+ MOD_LCANNON,
+ MOD_LCANNON_SPLASH,
+ MOD_FLAMER,
+ MOD_FLAMER_SPLASH,
+ MOD_GRENADE,
+ MOD_WATER,
+ MOD_SLIME,
+ MOD_LAVA,
+ MOD_CRUSH,
+ MOD_TELEFRAG,
+ MOD_FALLING,
+ MOD_SUICIDE,
+ MOD_TARGET_LASER,
+ MOD_TRIGGER_HURT,
+
+ MOD_ABUILDER_CLAW,
+ MOD_LEVEL0_BITE,
+ MOD_LEVEL1_CLAW,
+ MOD_LEVEL1_PCLOUD,
+ MOD_LEVEL3_CLAW,
+ MOD_LEVEL3_POUNCE,
+ MOD_LEVEL3_BOUNCEBALL,
+ MOD_LEVEL2_CLAW,
+ MOD_LEVEL2_ZAP,
+ MOD_LEVEL4_CLAW,
+ MOD_LEVEL4_CHARGE,
+
+ MOD_SLOWBLOB,
+ MOD_POISON,
+ MOD_SWARM,
+
+ MOD_HSPAWN,
+ MOD_TESLAGEN,
+ MOD_MGTURRET,
+ MOD_REACTOR,
+
+ MOD_ASPAWN,
+ MOD_ATUBE,
+ MOD_OVERMIND,
+ MOD_SLAP
+} meansOfDeath_t;
+
+
+//---------------------------------------------------------
+
+//TA: player class record
+typedef struct
+{
+ int classNum;
+
+ char *className;
+ char *humanName;
+
+ char *modelName;
+ float modelScale;
+ char *skinName;
+ float shadowScale;
+
+ char *hudName;
+
+ int stages;
+
+ vec3_t mins;
+ vec3_t maxs;
+ vec3_t crouchMaxs;
+ vec3_t deadMins;
+ vec3_t deadMaxs;
+ float zOffset;
+
+ int viewheight;
+ int crouchViewheight;
+
+ int health;
+ float fallDamage;
+ int regenRate;
+
+ int abilities;
+
+ weapon_t startWeapon;
+
+ float buildDist;
+
+ int fov;
+ float bob;
+ float bobCycle;
+ int steptime;
+
+ float speed;
+ float acceleration;
+ float airAcceleration;
+ float friction;
+ float stopSpeed;
+ float jumpMagnitude;
+ float knockbackScale;
+
+ int children[ 3 ];
+ int cost;
+ int value;
+} classAttributes_t;
+
+typedef struct
+{
+ char modelName[ MAX_QPATH ];
+ float modelScale;
+ char skinName[ MAX_QPATH ];
+ float shadowScale;
+ char hudName[ MAX_QPATH ];
+ char humanName[ MAX_STRING_CHARS ];
+
+ vec3_t mins;
+ vec3_t maxs;
+ vec3_t crouchMaxs;
+ vec3_t deadMins;
+ vec3_t deadMaxs;
+ int viewheight;
+ int crouchViewheight;
+ float zOffset;
+} classAttributeOverrides_t;
+
+//stages
+typedef enum
+{
+ S1,
+ S2,
+ S3
+} stage_t;
+
+#define MAX_BUILDABLE_MODELS 4
+
+//TA: buildable item record
+typedef struct
+{
+ int buildNum;
+
+ char *buildName;
+ char *humanName;
+ char *entityName;
+
+ char *models[ MAX_BUILDABLE_MODELS ];
+ float modelScale;
+
+ vec3_t mins;
+ vec3_t maxs;
+ float zOffset;
+
+ trType_t traj;
+ float bounce;
+
+ int buildPoints;
+ int stages;
+
+ int health;
+ int regenRate;
+
+ int splashDamage;
+ int splashRadius;
+
+ int meansOfDeath;
+
+ int team;
+ weapon_t buildWeapon;
+
+ int idleAnim;
+
+ int nextthink;
+ int buildTime;
+ qboolean usable;
+
+ int turretRange;
+ int turretFireSpeed;
+ weapon_t turretProjType;
+
+ float minNormal;
+ qboolean invertNormal;
+
+ qboolean creepTest;
+ int creepSize;
+
+ qboolean dccTest;
+ qboolean transparentTest;
+ qboolean reactorTest;
+ qboolean replaceable;
+} buildableAttributes_t;
+
+typedef struct
+{
+ char models[ MAX_BUILDABLE_MODELS ][ MAX_QPATH ];
+
+ float modelScale;
+ vec3_t mins;
+ vec3_t maxs;
+ float zOffset;
+} buildableAttributeOverrides_t;
+
+//TA: weapon record
+typedef struct
+{
+ int weaponNum;
+
+ int price;
+ int stages;
+
+ int slots;
+
+ char *weaponName;
+ char *weaponHumanName;
+
+ int maxAmmo;
+ int maxClips;
+ qboolean infiniteAmmo;
+ qboolean usesEnergy;
+
+ int repeatRate1;
+ int repeatRate2;
+ int repeatRate3;
+ int reloadTime;
+ float knockbackScale;
+
+ qboolean hasAltMode;
+ qboolean hasThirdMode;
+
+ qboolean canZoom;
+ float zoomFov;
+
+ qboolean purchasable;
+ qboolean longRanged;
+
+ int buildDelay;
+
+ WUTeam_t team;
+} weaponAttributes_t;
+
+//TA: upgrade record
+typedef struct
+{
+ int upgradeNum;
+
+ int price;
+ int stages;
+
+ int slots;
+
+ char *upgradeName;
+ char *upgradeHumanName;
+
+ char *icon;
+
+ qboolean purchasable;
+ qboolean usable;
+
+ WUTeam_t team;
+} upgradeAttributes_t;
+
+
+//TA:
+qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips );
+void BG_AddWeaponToInventory( int weapon, int stats[ ] );
+void BG_RemoveWeaponFromInventory( int weapon, int stats[ ] );
+qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] );
+void BG_AddUpgradeToInventory( int item, int stats[ ] );
+void BG_RemoveUpgradeFromInventory( int item, int stats[ ] );
+qboolean BG_InventoryContainsUpgrade( int item, int stats[ ] );
+void BG_ActivateUpgrade( int item, int stats[ ] );
+void BG_DeactivateUpgrade( int item, int stats[ ] );
+qboolean BG_UpgradeIsActive( int item, int stats[ ] );
+qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ],
+ vec3_t outAxis[ 3 ], qboolean inverse, qboolean ceiling );
+void BG_PositionBuildableRelativeToPlayer( const playerState_t *ps,
+ const vec3_t mins, const vec3_t maxs,
+ void (*trace)( trace_t *, const vec3_t, const vec3_t,
+ const vec3_t, const vec3_t, int, int ),
+ vec3_t outOrigin, vec3_t outAngles, trace_t *tr );
+int BG_GetValueOfHuman( playerState_t *ps );
+int BG_GetValueOfEquipment( playerState_t *ps );
+
+int BG_FindBuildNumForName( char *name );
+int BG_FindBuildNumForEntityName( char *name );
+char *BG_FindNameForBuildable( int bclass );
+char *BG_FindHumanNameForBuildable( int bclass );
+char *BG_FindEntityNameForBuildable( int bclass );
+char *BG_FindModelsForBuildable( int bclass, int modelNum );
+float BG_FindModelScaleForBuildable( int bclass );
+void BG_FindBBoxForBuildable( int bclass, vec3_t mins, vec3_t maxs );
+float BG_FindZOffsetForBuildable( int pclass );
+int BG_FindHealthForBuildable( int bclass );
+int BG_FindRegenRateForBuildable( int bclass );
+trType_t BG_FindTrajectoryForBuildable( int bclass );
+float BG_FindBounceForBuildable( int bclass );
+int BG_FindBuildPointsForBuildable( int bclass );
+qboolean BG_FindStagesForBuildable( int bclass, stage_t stage );
+int BG_FindSplashDamageForBuildable( int bclass );
+int BG_FindSplashRadiusForBuildable( int bclass );
+int BG_FindMODForBuildable( int bclass );
+int BG_FindTeamForBuildable( int bclass );
+weapon_t BG_FindBuildWeaponForBuildable( int bclass );
+int BG_FindAnimForBuildable( int bclass );
+int BG_FindNextThinkForBuildable( int bclass );
+int BG_FindBuildTimeForBuildable( int bclass );
+qboolean BG_FindUsableForBuildable( int bclass );
+int BG_FindRangeForBuildable( int bclass );
+int BG_FindFireSpeedForBuildable( int bclass );
+weapon_t BG_FindProjTypeForBuildable( int bclass );
+float BG_FindMinNormalForBuildable( int bclass );
+qboolean BG_FindInvertNormalForBuildable( int bclass );
+int BG_FindCreepTestForBuildable( int bclass );
+int BG_FindCreepSizeForBuildable( int bclass );
+int BG_FindDCCTestForBuildable( int bclass );
+int BG_FindUniqueTestForBuildable( int bclass );
+qboolean BG_FindReplaceableTestForBuildable( int bclass );
+qboolean BG_FindTransparentTestForBuildable( int bclass );
+void BG_InitBuildableOverrides( void );
+
+int BG_FindClassNumForName( char *name );
+char *BG_FindNameForClassNum( int pclass );
+char *BG_FindHumanNameForClassNum( int pclass );
+char *BG_FindModelNameForClass( int pclass );
+float BG_FindModelScaleForClass( int pclass );
+char *BG_FindSkinNameForClass( int pclass );
+float BG_FindShadowScaleForClass( int pclass );
+char *BG_FindHudNameForClass( int pclass );
+qboolean BG_FindStagesForClass( int pclass, stage_t stage );
+void BG_FindBBoxForClass( int pclass, vec3_t mins, vec3_t maxs, vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs );
+float BG_FindZOffsetForClass( int pclass );
+void BG_FindViewheightForClass( int pclass, int *viewheight, int *cViewheight );
+int BG_FindHealthForClass( int pclass );
+float BG_FindFallDamageForClass( int pclass );
+int BG_FindRegenRateForClass( int pclass );
+int BG_FindFovForClass( int pclass );
+float BG_FindBobForClass( int pclass );
+float BG_FindBobCycleForClass( int pclass );
+float BG_FindSpeedForClass( int pclass );
+float BG_FindAccelerationForClass( int pclass );
+float BG_FindAirAccelerationForClass( int pclass );
+float BG_FindFrictionForClass( int pclass );
+float BG_FindStopSpeedForClass( int pclass );
+float BG_FindJumpMagnitudeForClass( int pclass );
+float BG_FindKnockbackScaleForClass( int pclass );
+int BG_FindSteptimeForClass( int pclass );
+qboolean BG_ClassHasAbility( int pclass, int ability );
+weapon_t BG_FindStartWeaponForClass( int pclass );
+float BG_FindBuildDistForClass( int pclass );
+int BG_ClassCanEvolveFromTo( int fclass, int tclass, int credits, int num );
+int BG_FindCostOfClass( int pclass );
+int BG_FindValueOfClass( int pclass );
+void BG_InitClassOverrides( void );
+
+int BG_FindPriceForWeapon( int weapon );
+qboolean BG_FindStagesForWeapon( int weapon, stage_t stage );
+int BG_FindSlotsForWeapon( int weapon );
+char *BG_FindNameForWeapon( int weapon );
+int BG_FindWeaponNumForName( char *name );
+char *BG_FindHumanNameForWeapon( int weapon );
+char *BG_FindModelsForWeapon( int weapon, int modelNum );
+char *BG_FindIconForWeapon( int weapon );
+char *BG_FindCrosshairForWeapon( int weapon );
+int BG_FindCrosshairSizeForWeapon( int weapon );
+void BG_FindAmmoForWeapon( int weapon, int *maxAmmo, int *maxClips );
+qboolean BG_FindInfinteAmmoForWeapon( int weapon );
+qboolean BG_FindUsesEnergyForWeapon( int weapon );
+int BG_FindRepeatRate1ForWeapon( int weapon );
+int BG_FindRepeatRate2ForWeapon( int weapon );
+int BG_FindRepeatRate3ForWeapon( int weapon );
+int BG_FindReloadTimeForWeapon( int weapon );
+float BG_FindKnockbackScaleForWeapon( int weapon );
+qboolean BG_WeaponHasAltMode( int weapon );
+qboolean BG_WeaponHasThirdMode( int weapon );
+qboolean BG_WeaponCanZoom( int weapon );
+float BG_FindZoomFovForWeapon( int weapon );
+qboolean BG_FindPurchasableForWeapon( int weapon );
+qboolean BG_FindLongRangedForWeapon( int weapon );
+int BG_FindBuildDelayForWeapon( int weapon );
+WUTeam_t BG_FindTeamForWeapon( int weapon );
+
+int BG_FindPriceForUpgrade( int upgrade );
+qboolean BG_FindStagesForUpgrade( int upgrade, stage_t stage );
+int BG_FindSlotsForUpgrade( int upgrade );
+char *BG_FindNameForUpgrade( int upgrade );
+int BG_FindUpgradeNumForName( char *name );
+char *BG_FindHumanNameForUpgrade( int upgrade );
+char *BG_FindIconForUpgrade( int upgrade );
+qboolean BG_FindPurchasableForUpgrade( int upgrade );
+qboolean BG_FindUsableForUpgrade( int upgrade );
+WUTeam_t BG_FindTeamForUpgrade( int upgrade );
+
+// content masks
+#define MASK_ALL (-1)
+#define MASK_SOLID (CONTENTS_SOLID)
+#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY)
+#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP)
+#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME)
+#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA)
+#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY)
+
+
+//
+// entityState_t->eType
+//
+typedef enum
+{
+ ET_GENERAL,
+ ET_PLAYER,
+ ET_ITEM,
+
+ ET_BUILDABLE, //TA: buildable type
+
+ ET_MISSILE,
+ ET_MOVER,
+ ET_BEAM,
+ ET_PORTAL,
+ ET_SPEAKER,
+ ET_PUSH_TRIGGER,
+ ET_TELEPORT_TRIGGER,
+ ET_INVISIBLE,
+ ET_GRAPPLE, // grapple hooked on wall
+
+ ET_CORPSE,
+ ET_PARTICLE_SYSTEM,
+ ET_ANIMMAPOBJ,
+ ET_MODELDOOR,
+ ET_LIGHTFLARE,
+ ET_LEV2_ZAP_CHAIN,
+
+ ET_EVENTS // any of the EV_* events can be added freestanding
+ // by setting eType to ET_EVENTS + eventNum
+ // this avoids having to set eFlags and eventNum
+} entityType_t;
+
+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_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap );
+void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap );
+
+qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime );
+
+#define ARENAS_PER_TIER 4
+#define MAX_ARENAS 1024
+#define MAX_ARENAS_TEXT 8192
+
+#define MAX_BOTS 1024
+#define MAX_BOTS_TEXT 8192
+
+float atof_neg( char *token, qboolean allowNegative );
+int atoi_neg( char *token, qboolean allowNegative );
+
+void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weaponsSize,
+ upgrade_t *upgrades, int upgradesSize );
+void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSize );
+void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildablesSize );
+void BG_InitAllowedGameElements( void );
+qboolean BG_WeaponIsAllowed( weapon_t weapon );
+qboolean BG_UpgradeIsAllowed( upgrade_t upgrade );
+qboolean BG_ClassIsAllowed( pClass_t class );
+qboolean BG_BuildableIsAllowed( buildable_t buildable );
+qboolean BG_UpgradeClassAvailable( playerState_t *ps );
+
+typedef struct
+{
+ unsigned int hi;
+ unsigned int lo;
+} clientList_t;
+qboolean BG_ClientListTest( clientList_t *list, int clientNum );
+void BG_ClientListAdd( clientList_t *list, int clientNum );
+void BG_ClientListRemove( clientList_t *list, int clientNum );
+char *BG_ClientListString( clientList_t *list );
+void BG_ClientListParse( clientList_t *list, const char *s );
+
+// Friendly Fire Flags
+#define FFF_HUMANS 1
+#define FFF_ALIENS 2
+#define FFF_BUILDABLES 4
+
diff --git a/src/game/bg_slidemove.c b/src/game/bg_slidemove.c
new file mode 100644
index 0000000..aa32a6a
--- /dev/null
+++ b/src/game/bg_slidemove.c
@@ -0,0 +1,416 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// bg_slidemove.c -- part of bg_pmove functionality
+
+#include "../qcommon/q_shared.h"
+#include "bg_public.h"
+#include "bg_local.h"
+
+/*
+
+input: origin, velocity, bounds, groundPlane, trace function
+
+output: origin, velocity, impacts, stairup boolean
+
+*/
+
+/*
+==================
+PM_SlideMove
+
+Returns qtrue if the velocity was clipped in some way
+==================
+*/
+#define MAX_CLIP_PLANES 5
+qboolean PM_SlideMove( qboolean gravity )
+{
+ int bumpcount, numbumps;
+ vec3_t dir;
+ float d;
+ int numplanes;
+ vec3_t planes[MAX_CLIP_PLANES];
+ vec3_t primal_velocity;
+ vec3_t clipVelocity;
+ int i, j, k;
+ trace_t trace;
+ vec3_t end;
+ float time_left;
+ float into;
+ vec3_t endVelocity;
+ vec3_t endClipVelocity;
+
+ numbumps = 4;
+
+ VectorCopy( pm->ps->velocity, primal_velocity );
+
+ if( gravity )
+ {
+ VectorCopy( pm->ps->velocity, endVelocity );
+ endVelocity[ 2 ] -= pm->ps->gravity * pml.frametime;
+ pm->ps->velocity[ 2 ] = ( pm->ps->velocity[ 2 ] + endVelocity[ 2 ] ) * 0.5;
+ primal_velocity[ 2 ] = endVelocity[ 2 ];
+
+ if( pml.groundPlane )
+ {
+ // slide along the ground plane
+ PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
+ pm->ps->velocity, OVERCLIP );
+ }
+ }
+
+ time_left = pml.frametime;
+
+ // never turn against the ground plane
+ if( pml.groundPlane )
+ {
+ numplanes = 1;
+ VectorCopy( pml.groundTrace.plane.normal, planes[ 0 ] );
+ }
+ else
+ numplanes = 0;
+
+ // never turn against original velocity
+ VectorNormalize2( pm->ps->velocity, planes[ numplanes ] );
+ numplanes++;
+
+ for( bumpcount = 0; bumpcount < numbumps; bumpcount++ )
+ {
+ // calculate position we are trying to move to
+ VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end );
+
+ // see if we can make it there
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask );
+
+ if( trace.allsolid )
+ {
+ // entity is completely trapped in another solid
+ pm->ps->velocity[ 2 ] = 0; // don't build up falling damage, but allow sideways acceleration
+ return qtrue;
+ }
+
+ if( trace.fraction > 0 )
+ {
+ // actually covered some distance
+ VectorCopy( trace.endpos, pm->ps->origin );
+ }
+
+ if( trace.fraction == 1 )
+ break; // moved the entire distance
+
+ // save entity for contact
+ PM_AddTouchEnt( trace.entityNum );
+
+ time_left -= time_left * trace.fraction;
+
+ if( numplanes >= MAX_CLIP_PLANES )
+ {
+ // this shouldn't really happen
+ VectorClear( pm->ps->velocity );
+ return qtrue;
+ }
+
+ //
+ // if this is the same plane we hit before, nudge velocity
+ // out along it, which fixes some epsilon issues with
+ // non-axial planes
+ //
+ for( i = 0 ; i < numplanes ; i++ )
+ {
+ if( DotProduct( trace.plane.normal, planes[i] ) > 0.99 )
+ {
+ VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity );
+ break;
+ }
+ }
+
+ if( i < numplanes )
+ continue;
+
+ VectorCopy( trace.plane.normal, planes[ numplanes ] );
+ numplanes++;
+
+ //
+ // modify velocity so it parallels all of the clip planes
+ //
+
+ // find a plane that it enters
+ for( i = 0; i < numplanes; i++ )
+ {
+ into = DotProduct( pm->ps->velocity, planes[ i ] );
+ if( into >= 0.1 )
+ continue; // move doesn't interact with the plane
+
+ // see how hard we are hitting things
+ if( -into > pml.impactSpeed )
+ pml.impactSpeed = -into;
+
+ // slide along the plane
+ PM_ClipVelocity( pm->ps->velocity, planes[ i ], clipVelocity, OVERCLIP );
+
+ // slide along the plane
+ PM_ClipVelocity( endVelocity, planes[ i ], endClipVelocity, OVERCLIP );
+
+ // see if there is a second plane that the new move enters
+ for( j = 0; j < numplanes; j++ )
+ {
+ if( j == i )
+ continue;
+
+ if( DotProduct( clipVelocity, planes[ j ] ) >= 0.1 )
+ continue; // move doesn't interact with the plane
+
+ // try clipping the move to the plane
+ PM_ClipVelocity( clipVelocity, planes[ j ], clipVelocity, OVERCLIP );
+ PM_ClipVelocity( endClipVelocity, planes[ j ], endClipVelocity, OVERCLIP );
+
+ // see if it goes back into the first clip plane
+ if( DotProduct( clipVelocity, planes[ i ] ) >= 0 )
+ continue;
+
+ // slide the original velocity along the crease
+ CrossProduct( planes[ i ], planes[ j ], dir );
+ VectorNormalize( dir );
+ d = DotProduct( dir, pm->ps->velocity );
+ VectorScale( dir, d, clipVelocity );
+
+ CrossProduct( planes[ i ], planes[ j ], dir);
+ VectorNormalize( dir );
+ d = DotProduct( dir, endVelocity );
+ VectorScale( dir, d, endClipVelocity );
+
+ // see if there is a third plane the the new move enters
+ for( k = 0; k < numplanes; k++ )
+ {
+ if( k == i || k == j )
+ continue;
+
+ if( DotProduct( clipVelocity, planes[ k ] ) >= 0.1 )
+ continue; // move doesn't interact with the plane
+
+ // stop dead at a tripple plane interaction
+ VectorClear( pm->ps->velocity );
+ return qtrue;
+ }
+ }
+
+ // if we have fixed all interactions, try another move
+ VectorCopy( clipVelocity, pm->ps->velocity );
+ VectorCopy( endClipVelocity, endVelocity );
+ break;
+ }
+ }
+
+ if( gravity )
+ VectorCopy( endVelocity, pm->ps->velocity );
+
+ // don't change velocity if in a timer (FIXME: is this correct?)
+ if( pm->ps->pm_time )
+ VectorCopy( primal_velocity, pm->ps->velocity );
+
+ return ( bumpcount != 0 );
+}
+
+/*
+==================
+PM_StepEvent
+==================
+*/
+void PM_StepEvent( vec3_t from, vec3_t to, vec3_t normal )
+{
+ float size;
+ vec3_t delta, dNormal;
+
+ VectorSubtract( from, to, delta );
+ VectorCopy( delta, dNormal );
+ VectorNormalize( dNormal );
+
+ size = DotProduct( normal, dNormal ) * VectorLength( delta );
+
+ if( size > 0.0f )
+ {
+ if( size > 2.0f )
+ {
+ if( size < 7.0f )
+ PM_AddEvent( EV_STEPDN_4 );
+ else if( size < 11.0f )
+ PM_AddEvent( EV_STEPDN_8 );
+ else if( size < 15.0f )
+ PM_AddEvent( EV_STEPDN_12 );
+ else
+ PM_AddEvent( EV_STEPDN_16 );
+ }
+ }
+ else
+ {
+ size = fabs( size );
+
+ if( size > 2.0f )
+ {
+ if( size < 7.0f )
+ PM_AddEvent( EV_STEP_4 );
+ else if( size < 11.0f )
+ PM_AddEvent( EV_STEP_8 );
+ else if( size < 15.0f )
+ PM_AddEvent( EV_STEP_12 );
+ else
+ PM_AddEvent( EV_STEP_16 );
+ }
+ }
+
+ if( pm->debugLevel )
+ Com_Printf( "%i:stepped\n", c_pmove );
+}
+
+/*
+==================
+PM_StepSlideMove
+==================
+*/
+qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive )
+{
+ vec3_t start_o, start_v;
+ vec3_t down_o, down_v;
+ trace_t trace;
+ vec3_t normal;
+ vec3_t step_v, step_vNormal;
+ vec3_t up, down;
+ float stepSize;
+ qboolean stepped = qfalse;
+
+ if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( pm->ps->grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+ VectorCopy( pm->ps->origin, start_o );
+ VectorCopy( pm->ps->velocity, start_v );
+
+ if( PM_SlideMove( gravity ) == 0 )
+ {
+ VectorCopy( start_o, down );
+ VectorMA( down, -STEPSIZE, normal, down );
+ pm->trace( &trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask );
+
+ //we can step down
+ if( trace.fraction > 0.01f && trace.fraction < 1.0f &&
+ !trace.allsolid && pml.groundPlane != qfalse )
+ {
+ if( pm->debugLevel )
+ Com_Printf( "%d: step down\n", c_pmove );
+
+ stepped = qtrue;
+ }
+ }
+ else
+ {
+ VectorCopy( start_o, down );
+ VectorMA( down, -STEPSIZE, normal, down );
+ pm->trace( &trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask );
+ // never step up when you still have up velocity
+ if( DotProduct( trace.plane.normal, pm->ps->velocity ) > 0.0f &&
+ ( trace.fraction == 1.0f || DotProduct( trace.plane.normal, normal ) < 0.7f ) )
+ {
+ return stepped;
+ }
+
+ VectorCopy( pm->ps->origin, down_o );
+ VectorCopy( pm->ps->velocity, down_v );
+
+ VectorCopy( start_o, up );
+ VectorMA( up, STEPSIZE, normal, up );
+
+ // test the player position if they were a stepheight higher
+ pm->trace( &trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask );
+ if( trace.allsolid )
+ {
+ if( pm->debugLevel )
+ Com_Printf( "%i:bend can't step\n", c_pmove );
+
+ return stepped; // can't step up
+ }
+
+ VectorSubtract( trace.endpos, start_o, step_v );
+ VectorCopy( step_v, step_vNormal );
+ VectorNormalize( step_vNormal );
+
+ stepSize = DotProduct( normal, step_vNormal ) * VectorLength( step_v );
+ // try slidemove from this position
+ VectorCopy( trace.endpos, pm->ps->origin );
+ VectorCopy( start_v, pm->ps->velocity );
+
+ if( PM_SlideMove( gravity ) == 0 )
+ {
+ if( pm->debugLevel )
+ Com_Printf( "%d: step up\n", c_pmove );
+
+ stepped = qtrue;
+ }
+
+ // push down the final amount
+ VectorCopy( pm->ps->origin, down );
+ VectorMA( down, -stepSize, normal, down );
+ pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask );
+
+ if( !trace.allsolid )
+ VectorCopy( trace.endpos, pm->ps->origin );
+
+ if( trace.fraction < 1.0f )
+ PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP );
+ }
+
+ if( !predictive && stepped )
+ PM_StepEvent( start_o, pm->ps->origin, normal );
+
+ return stepped;
+}
+
+/*
+==================
+PM_PredictStepMove
+==================
+*/
+qboolean PM_PredictStepMove( void )
+{
+ vec3_t velocity, origin;
+ float impactSpeed;
+ qboolean stepped = qfalse;
+
+ VectorCopy( pm->ps->velocity, velocity );
+ VectorCopy( pm->ps->origin, origin );
+ impactSpeed = pml.impactSpeed;
+
+ if( PM_StepSlideMove( qfalse, qtrue ) )
+ stepped = qtrue;
+
+ VectorCopy( velocity, pm->ps->velocity );
+ VectorCopy( origin, pm->ps->origin );
+ pml.impactSpeed = impactSpeed;
+
+ return stepped;
+}
diff --git a/src/game/g_active.c b/src/game/g_active.c
new file mode 100644
index 0000000..d6a0bb1
--- /dev/null
+++ b/src/game/g_active.c
@@ -0,0 +1,2033 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+/*
+===============
+G_DamageFeedback
+
+Called just before a snapshot is sent to the given player.
+Totals up all damage and generates both the player_state_t
+damage values to that client for pain blends and kicks, and
+global pain sound events for all clients.
+===============
+*/
+void P_DamageFeedback( gentity_t *player )
+{
+ gclient_t *client;
+ float count;
+ vec3_t angles;
+
+ client = player->client;
+ if( client->ps.pm_type == PM_DEAD )
+ return;
+
+ // total points of damage shot at the player this frame
+ count = client->damage_blood + client->damage_armor;
+ if( count == 0 )
+ return; // didn't take any damage
+
+ if( count > 255 )
+ count = 255;
+
+ // send the information to the client
+
+ // world damage (falling, slime, etc) uses a special code
+ // to make the blend blob centered instead of positional
+ if( client->damage_fromWorld )
+ {
+ client->ps.damagePitch = 255;
+ client->ps.damageYaw = 255;
+
+ client->damage_fromWorld = qfalse;
+ }
+ else
+ {
+ vectoangles( client->damage_from, angles );
+ client->ps.damagePitch = angles[ PITCH ] / 360.0 * 256;
+ client->ps.damageYaw = angles[ YAW ] / 360.0 * 256;
+ }
+
+ // play an apropriate pain sound
+ if( ( level.time > player->pain_debounce_time ) && !( player->flags & FL_GODMODE ) )
+ {
+ player->pain_debounce_time = level.time + 700;
+ G_AddEvent( player, EV_PAIN, player->health > 255 ? 255 : player->health );
+ client->ps.damageEvent++;
+ }
+
+
+ client->ps.damageCount = count;
+
+ //
+ // clear totals
+ //
+ client->damage_blood = 0;
+ client->damage_armor = 0;
+ client->damage_knockback = 0;
+}
+
+
+
+/*
+=============
+P_WorldEffects
+
+Check for lava / slime contents and drowning
+=============
+*/
+void P_WorldEffects( gentity_t *ent )
+{
+ int waterlevel;
+
+ if( ent->client->noclip )
+ {
+ ent->client->airOutTime = level.time + 12000; // don't need air
+ return;
+ }
+
+ waterlevel = ent->waterlevel;
+
+ //
+ // check for drowning
+ //
+ if( waterlevel == 3 )
+ {
+ // if out of air, start drowning
+ if( ent->client->airOutTime < level.time)
+ {
+ // drown!
+ ent->client->airOutTime += 1000;
+ if( ent->health > 0 )
+ {
+ // take more damage the longer underwater
+ ent->damage += 2;
+ if( ent->damage > 15 )
+ ent->damage = 15;
+
+ // play a gurp sound instead of a normal pain sound
+ if( ent->health <= ent->damage )
+ G_Sound( ent, CHAN_VOICE, G_SoundIndex( "*drown.wav" ) );
+ else if( rand( ) & 1 )
+ G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) );
+ else
+ G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) );
+
+ // don't play a normal pain sound
+ ent->pain_debounce_time = level.time + 200;
+
+ G_Damage( ent, NULL, NULL, NULL, NULL,
+ ent->damage, DAMAGE_NO_ARMOR, MOD_WATER );
+ }
+ }
+ }
+ else
+ {
+ ent->client->airOutTime = level.time + 12000;
+ ent->damage = 2;
+ }
+
+ //
+ // check for sizzle damage (move to pmove?)
+ //
+ if( waterlevel &&
+ ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) )
+ {
+ if( ent->health > 0 &&
+ ent->pain_debounce_time <= level.time )
+ {
+ if( ent->watertype & CONTENTS_LAVA )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL,
+ 30 * waterlevel, 0, MOD_LAVA );
+ }
+
+ if( ent->watertype & CONTENTS_SLIME )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL,
+ 10 * waterlevel, 0, MOD_SLIME );
+ }
+ }
+ }
+}
+
+
+
+/*
+===============
+G_SetClientSound
+===============
+*/
+void G_SetClientSound( gentity_t *ent )
+{
+ if( ent->waterlevel && ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) )
+ ent->client->ps.loopSound = level.snd_fry;
+ else
+ ent->client->ps.loopSound = 0;
+}
+
+
+
+//==============================================================
+
+static void G_ClientShove( gentity_t *ent, gentity_t *victim )
+{
+ vec3_t dir, push;
+ int entMass = 200, vicMass = 200;
+
+ // shoving enemies changes gameplay too much
+ if( !OnSameTeam( ent, victim ) )
+ return;
+
+ if ( ( victim->client->ps.weapon >= WP_ABUILD ) &&
+ ( victim->client->ps.weapon <= WP_HBUILD ) &&
+ ( victim->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) )
+ return;
+
+ // alien mass is directly related to their health points
+ // human mass is 200, double for bsuit
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ {
+ entMass = BG_FindHealthForClass( ent->client->pers.classSelection );
+ }
+ else if( ent->client->pers.teamSelection == PTE_HUMANS )
+ {
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) )
+ entMass *= 2;
+ }
+ else
+ return;
+
+ if( victim->client->pers.teamSelection == PTE_ALIENS )
+ {
+ vicMass = BG_FindHealthForClass( victim->client->pers.classSelection );
+ }
+ else if( BG_InventoryContainsUpgrade( UP_BATTLESUIT,
+ victim->client->ps.stats ) )
+ {
+ vicMass *= 2;
+ }
+
+ if( vicMass <= 0 || entMass <= 0 )
+ return;
+
+ VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, dir );
+ VectorNormalizeFast( dir );
+
+ // don't break the dretch elevator
+ if( fabs( dir[ 2 ] ) > fabs( dir[ 0 ] ) && fabs( dir[ 2 ] ) > fabs( dir[ 1 ] ) )
+ return;
+
+ VectorScale( dir,
+ ( g_shove.value * ( ( float )entMass / ( float )vicMass ) ), push );
+ VectorAdd( victim->client->ps.velocity, push,
+ victim->client->ps.velocity );
+
+}
+
+/*
+==============
+ClientImpacts
+==============
+*/
+void ClientImpacts( gentity_t *ent, pmove_t *pm )
+{
+ int i, j;
+ trace_t trace;
+ gentity_t *other;
+
+ memset( &trace, 0, sizeof( trace ) );
+
+ for( i = 0; i < pm->numtouch; i++ )
+ {
+ for( j = 0; j < i; j++ )
+ {
+ if( pm->touchents[ j ] == pm->touchents[ i ] )
+ break;
+ }
+
+ if( j != i )
+ continue; // duplicated
+
+ other = &g_entities[ pm->touchents[ i ] ];
+
+ // see G_UnlaggedDetectCollisions(), this is the inverse of that.
+ // if our movement is blocked by another player's real position,
+ // don't use the unlagged position for them because they are
+ // blocking or server-side Pmove() from reaching it
+ if( other->client && other->client->unlaggedCalc.used )
+ other->client->unlaggedCalc.used = qfalse;
+
+ //charge attack
+ if( ent->client->ps.weapon == WP_ALEVEL4 &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 &&
+ ent->client->charging )
+ ChargeAttack( ent, other );
+
+ if( ent->client && other->client )
+ G_ClientShove( ent, other );
+
+ if( !other->touch )
+ continue;
+
+ other->touch( other, ent, &trace );
+ }
+}
+
+/*
+============
+G_TouchTriggers
+
+Find all trigger entities that ent's current position touches.
+Spectators will only interact with teleporters.
+============
+*/
+void G_TouchTriggers( gentity_t *ent )
+{
+ int i, num;
+ int touch[MAX_GENTITIES];
+ gentity_t *hit;
+ trace_t trace;
+ vec3_t mins, maxs;
+ vec3_t pmins, pmaxs;
+ static vec3_t range = { 10, 10, 10 };
+
+ if( !ent->client )
+ return;
+
+ // dead clients don't activate triggers!
+ if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ],
+ pmins, pmaxs, NULL, NULL, NULL );
+
+ VectorAdd( ent->client->ps.origin, pmins, mins );
+ VectorAdd( ent->client->ps.origin, pmaxs, maxs );
+
+ VectorSubtract( mins, range, mins );
+ VectorAdd( maxs, range, maxs );
+
+ num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
+
+ // can't use ent->absmin, because that has a one unit pad
+ VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
+ VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
+
+ for( i = 0; i < num; i++ )
+ {
+ hit = &g_entities[ touch[ i ] ];
+
+ if( !hit->touch && !ent->touch )
+ continue;
+
+ if( !( hit->r.contents & CONTENTS_TRIGGER ) )
+ continue;
+
+ // ignore most entities if a spectator
+ if( ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) ||
+ ( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ||
+ ( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) )
+ {
+ if( hit->s.eType != ET_TELEPORT_TRIGGER &&
+ // this is ugly but adding a new ET_? type will
+ // most likely cause network incompatibilities
+ hit->touch != Touch_DoorTrigger )
+ {
+ //check for manually triggered doors
+ manualTriggerSpectator( hit, ent );
+ continue;
+ }
+ }
+
+ if( !trap_EntityContact( mins, maxs, hit ) )
+ continue;
+
+ memset( &trace, 0, sizeof( trace ) );
+
+ if( hit->touch )
+ hit->touch( hit, ent, &trace );
+ }
+}
+
+/*
+=================
+SpectatorThink
+=================
+*/
+void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
+{
+ pmove_t pm;
+ gclient_t *client;
+ qboolean attack1, attack3;
+ qboolean doPmove = qtrue;
+
+ client = ent->client;
+
+ client->oldbuttons = client->buttons;
+ client->buttons = ucmd->buttons;
+
+ attack1 = ( ( client->buttons & BUTTON_ATTACK ) &&
+ !( client->oldbuttons & BUTTON_ATTACK ) );
+ attack3 = ( ( client->buttons & BUTTON_USE_HOLDABLE ) &&
+ !( client->oldbuttons & BUTTON_USE_HOLDABLE ) );
+
+ if( level.mapRotationVoteTime )
+ {
+ if( attack1 )
+ {
+ G_IntermissionMapVoteCommand( ent, qtrue, qfalse );
+ attack1 = qfalse;
+ }
+ if( ( client->buttons & BUTTON_ATTACK2 ) && !( client->oldbuttons & BUTTON_ATTACK2 ) )
+ G_IntermissionMapVoteCommand( ent, qfalse, qfalse );
+ }
+
+ if( client->sess.spectatorState == SPECTATOR_LOCKED || client->sess.spectatorState == SPECTATOR_FOLLOW )
+ client->ps.pm_type = PM_FREEZE;
+ else
+ client->ps.pm_type = PM_SPECTATOR;
+
+ if ( client->sess.spectatorState == SPECTATOR_FOLLOW )
+ {
+ gclient_t *cl;
+ if ( client->sess.spectatorClient >= 0 )
+ {
+ cl = &level.clients[ client->sess.spectatorClient ];
+ if ( cl->sess.sessionTeam != TEAM_SPECTATOR )
+ doPmove = qfalse;
+ }
+ }
+
+ if (doPmove)
+ {
+ client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
+
+ // in case the client entered the queue while following a teammate
+ if( ( client->pers.teamSelection == PTE_ALIENS &&
+ G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) ||
+ ( client->pers.teamSelection == PTE_HUMANS &&
+ G_SearchSpawnQueue( &level.humanSpawnQueue, ent-g_entities ) ) )
+ {
+ client->ps.pm_flags |= PMF_QUEUED;
+ }
+
+
+ client->ps.stats[ STAT_STAMINA ] = 0;
+ client->ps.stats[ STAT_MISC ] = 0;
+ client->ps.stats[ STAT_BUILDABLE ] = 0;
+ client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
+ client->ps.weapon = WP_NONE;
+
+ // set up for pmove
+ memset( &pm, 0, sizeof( pm ) );
+ pm.ps = &client->ps;
+ pm.cmd = *ucmd;
+ pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies
+ pm.trace = trap_Trace;
+ pm.pointcontents = trap_PointContents;
+
+ // perform a pmove
+ Pmove( &pm );
+
+ // save results of pmove
+ VectorCopy( client->ps.origin, ent->s.origin );
+
+ G_TouchTriggers( ent );
+ trap_UnlinkEntity( ent );
+
+ if( ( attack1 ) && ( client->ps.pm_flags & PMF_QUEUED ) )
+ {
+ if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
+ else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
+
+ client->pers.classSelection = PCL_NONE;
+ client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
+ }
+
+ if( attack1 && client->pers.classSelection == PCL_NONE )
+ {
+ if( client->pers.teamSelection == PTE_NONE )
+ G_TriggerMenu( client->ps.clientNum, MN_TEAM );
+ else if( client->pers.teamSelection == PTE_ALIENS )
+ G_TriggerMenu( client->ps.clientNum, MN_A_CLASS );
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );
+ }
+
+ //set the queue position for the client side
+ if( client->ps.pm_flags & PMF_QUEUED )
+ {
+ if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ client->ps.persistant[ PERS_QUEUEPOS ] =
+ G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
+ }
+ else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ client->ps.persistant[ PERS_QUEUEPOS ] =
+ G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
+ }
+ }
+ }
+
+ else if( attack1 && ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ {
+ G_StopFollowing( ent );
+ client->pers.classSelection = PCL_NONE;
+ if( client->pers.teamSelection == PTE_NONE )
+ G_TriggerMenu( ent-g_entities, MN_TEAM );
+ else if( client->pers.teamSelection == PTE_ALIENS )
+ G_TriggerMenu( ent-g_entities, MN_A_CLASS );
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ G_TriggerMenu( ent-g_entities, MN_H_SPAWN );
+ }
+
+ if( attack3 )
+ {
+ G_ToggleFollow( ent );
+ }
+}
+
+
+
+/*
+=================
+ClientInactivityTimer
+
+Returns qfalse if the client is dropped
+=================
+*/
+qboolean ClientInactivityTimer( gclient_t *client )
+{
+ if( ! g_inactivity.integer )
+ {
+ // give everyone some time, so if the operator sets g_inactivity during
+ // gameplay, everyone isn't kicked
+ client->inactivityTime = level.time + 60 * 1000;
+ client->inactivityWarning = qfalse;
+ }
+ else if( client->pers.cmd.forwardmove ||
+ client->pers.cmd.rightmove ||
+ client->pers.cmd.upmove ||
+ ( client->pers.cmd.buttons & BUTTON_ATTACK ) )
+ {
+ client->inactivityTime = level.time + g_inactivity.integer * 1000;
+ client->inactivityWarning = qfalse;
+ }
+ else if( !client->pers.localClient )
+ {
+ if( level.time > client->inactivityTime )
+ {
+ trap_DropClient( client - level.clients, "Dropped due to inactivity" );
+ return qfalse;
+ }
+
+ if( level.time > client->inactivityTime - 10000 && !client->inactivityWarning )
+ {
+ client->inactivityWarning = qtrue;
+ trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
+ }
+ }
+
+ return qtrue;
+}
+
+/*
+==================
+ClientTimerActions
+
+Actions that happen once a second
+==================
+*/
+void ClientTimerActions( gentity_t *ent, int msec )
+{
+ gclient_t *client;
+ usercmd_t *ucmd;
+ int aForward, aRight;
+ qboolean walking = qfalse, stopped = qfalse,
+ crouched = qfalse, jumping = qfalse,
+ strafing = qfalse;
+
+ ucmd = &ent->client->pers.cmd;
+
+ aForward = abs( ucmd->forwardmove );
+ aRight = abs( ucmd->rightmove );
+
+ client = ent->client;
+ client->time100 += msec;
+ client->time1000 += msec;
+ client->time10000 += msec;
+
+ if( aForward == 0 && aRight == 0 )
+ stopped = qtrue;
+ else if( aForward <= 64 && aRight <= 64 )
+ walking = qtrue;
+
+ if( aRight > 0 )
+ strafing = qtrue;
+
+ if( ucmd->upmove > 0 )
+ jumping = qtrue;
+ else if( ent->client->ps.pm_flags & PMF_DUCKED )
+ crouched = qtrue;
+
+ while ( client->time100 >= 100 )
+ {
+ client->time100 -= 100;
+
+ //if not trying to run then not trying to sprint
+ if( walking || stopped )
+ client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
+
+ if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
+ client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
+
+ if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && !crouched )
+ {
+ //subtract stamina
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
+ client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE;
+ else
+ client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE;
+
+ if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA )
+ client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA;
+ }
+
+ if( walking || crouched )
+ {
+ //restore stamina
+ client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE;
+
+ if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
+ client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
+ }
+ else if( stopped )
+ {
+ //restore stamina faster
+ client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE;
+
+ if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
+ client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
+ }
+
+ //client is charging up for a pounce
+ if( client->ps.weapon == WP_ALEVEL3 || client->ps.weapon == WP_ALEVEL3_UPG )
+ {
+ int pounceSpeed = 0;
+
+ if( client->ps.weapon == WP_ALEVEL3 )
+ pounceSpeed = LEVEL3_POUNCE_SPEED;
+ else if( client->ps.weapon == WP_ALEVEL3_UPG )
+ pounceSpeed = LEVEL3_POUNCE_UPG_SPEED;
+
+ if( client->ps.stats[ STAT_MISC ] < pounceSpeed && ucmd->buttons & BUTTON_ATTACK2 )
+ client->ps.stats[ STAT_MISC ] += ( 100.0f / (float)LEVEL3_POUNCE_CHARGE_TIME ) * pounceSpeed;
+
+ if( !( ucmd->buttons & BUTTON_ATTACK2 ) )
+ {
+ if( client->pmext.pouncePayload > 0 )
+ client->allowedToPounce = qtrue;
+ }
+
+ if( client->ps.stats[ STAT_MISC ] > pounceSpeed )
+ client->ps.stats[ STAT_MISC ] = pounceSpeed;
+ }
+
+ //client is charging up for a... charge
+ if( client->ps.weapon == WP_ALEVEL4 )
+ {
+ if( client->ps.stats[ STAT_MISC ] < LEVEL4_CHARGE_TIME && ucmd->buttons & BUTTON_ATTACK2 &&
+ !client->charging )
+ {
+ client->charging = qfalse; //should already be off, just making sure
+ client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING;
+
+ if( ucmd->forwardmove > 0 )
+ {
+ //trigger charge sound...is quite annoying
+ //if( client->ps.stats[ STAT_MISC ] <= 0 )
+ // G_AddEvent( ent, EV_LEV4_CHARGE_PREPARE, 0 );
+
+ client->ps.stats[ STAT_MISC ] += (int)( 100 * (float)LEVEL4_CHARGE_CHARGE_RATIO );
+
+ if( client->ps.stats[ STAT_MISC ] > LEVEL4_CHARGE_TIME )
+ client->ps.stats[ STAT_MISC ] = LEVEL4_CHARGE_TIME;
+ }
+ else
+ client->ps.stats[ STAT_MISC ] = 0;
+ }
+
+ if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging ||
+ client->ps.stats[ STAT_MISC ] == LEVEL4_CHARGE_TIME )
+ {
+ if( client->ps.stats[ STAT_MISC ] > LEVEL4_MIN_CHARGE_TIME )
+ {
+ client->ps.stats[ STAT_MISC ] -= 100;
+
+ if( client->charging == qfalse )
+ G_AddEvent( ent, EV_LEV4_CHARGE_START, 0 );
+
+ client->charging = qtrue;
+ client->ps.stats[ STAT_STATE ] |= SS_CHARGING;
+
+ //if the charger has stopped moving take a chunk of charge away
+ if( VectorLength( client->ps.velocity ) < 64.0f || aRight )
+ client->ps.stats[ STAT_MISC ] = client->ps.stats[ STAT_MISC ] / 2;
+
+ //can't charge backwards
+ if( ucmd->forwardmove < 0 )
+ client->ps.stats[ STAT_MISC ] = 0;
+ }
+ else
+ client->ps.stats[ STAT_MISC ] = 0;
+
+
+ if( client->ps.stats[ STAT_MISC ] <= 0 )
+ {
+ client->ps.stats[ STAT_MISC ] = 0;
+ client->charging = qfalse;
+ client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING;
+ }
+ }
+ }
+
+ //client is charging up an lcannon
+ if( client->ps.weapon == WP_LUCIFER_CANNON )
+ {
+ int ammo;
+
+ ammo = client->ps.ammo;
+
+ if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE && ucmd->buttons & BUTTON_ATTACK )
+ client->ps.stats[ STAT_MISC ] += ( 100.0f / LCANNON_CHARGE_TIME ) * LCANNON_TOTAL_CHARGE;
+
+ if( client->ps.stats[ STAT_MISC ] > LCANNON_TOTAL_CHARGE )
+ client->ps.stats[ STAT_MISC ] = LCANNON_TOTAL_CHARGE;
+
+ if( client->ps.stats[ STAT_MISC ] > ( ammo * LCANNON_TOTAL_CHARGE ) / 10 )
+ client->ps.stats[ STAT_MISC ] = ammo * LCANNON_TOTAL_CHARGE / 10;
+ }
+
+ switch( client->ps.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ //set validity bit on buildable
+ if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
+ {
+ int dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+ vec3_t dummy;
+
+ if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT,
+ dist, dummy ) == IBE_NONE )
+ client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT;
+ else
+ client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT;
+ }
+
+ case WP_BLASTER:
+ //update build timer
+ if( client->ps.stats[ STAT_MISC ] > 0 )
+ client->ps.stats[ STAT_MISC ] -= 100;
+
+ if( client->ps.stats[ STAT_MISC ] < 0 )
+ client->ps.stats[ STAT_MISC ] = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE )
+ {
+ int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime );
+
+ if( remainingStartupTime < 0 )
+ {
+ if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ ent->client->medKitHealthToRestore &&
+ ent->client->ps.pm_type != PM_DEAD )
+ {
+ ent->client->medKitHealthToRestore--;
+ ent->health++;
+ }
+ else
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
+ }
+ else
+ {
+ if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ ent->client->medKitHealthToRestore &&
+ ent->client->ps.pm_type != PM_DEAD )
+ {
+ //partial increase
+ if( level.time > client->medKitIncrementTime )
+ {
+ ent->client->medKitHealthToRestore--;
+ ent->health++;
+
+ client->medKitIncrementTime = level.time +
+ ( remainingStartupTime / MEDKIT_STARTUP_SPEED );
+ }
+ }
+ else
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
+ }
+ }
+ }
+
+ while( client->time1000 >= 1000 )
+ {
+ client->time1000 -= 1000;
+
+ //client is poison clouded
+ if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED )
+ G_Damage( ent, client->lastPoisonCloudedClient, client->lastPoisonCloudedClient, NULL, NULL,
+ LEVEL1_PCLOUD_DMG, 0, MOD_LEVEL1_PCLOUD );
+
+ //client is poisoned
+ if( client->ps.stats[ STAT_STATE ] & SS_POISONED )
+ {
+ int damage = ALIEN_POISON_DMG;
+
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )
+ damage -= BSUIT_POISON_PROTECTION;
+ if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) )
+ damage -= HELMET_POISON_PROTECTION;
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
+ damage -= LIGHTARMOUR_POISON_PROTECTION;
+
+ G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL,
+ 0, damage, 0, MOD_POISON );
+ }
+
+ //replenish alien health
+ if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ level.surrenderTeam != PTE_ALIENS )
+ {
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *boostEntity;
+ float modifier = 1.0f;
+
+ VectorAdd( client->ps.origin, range, maxs );
+ VectorSubtract( client->ps.origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ boostEntity = &g_entities[ entityList[ i ] ];
+
+ if( boostEntity->client && boostEntity->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ boostEntity->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL4 )
+ {
+ modifier = LEVEL4_REGEN_MOD;
+ break;
+ }
+ else if( boostEntity->s.eType == ET_BUILDABLE &&
+ boostEntity->s.modelindex == BA_A_BOOSTER &&
+ boostEntity->spawned && boostEntity->health > 0 )
+ {
+ modifier = BOOSTER_REGEN_MOD;
+ break;
+ }
+ }
+
+ if( ent->health > 0 && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] &&
+ !level.paused &&
+ ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
+ {
+ ent->health += BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier;
+
+ // if completely healed, cancel retribution
+ if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] )
+ {
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ ent->client->tkcredits[ i ] = 0;
+ }
+ }
+
+ if( ent->health > client->ps.stats[ STAT_MAX_HEALTH ] )
+ ent->health = client->ps.stats[ STAT_MAX_HEALTH ];
+ }
+
+
+ if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ ent->client->pers.statscounters.timealive++;
+ level.alienStatsCounters.timealive++;
+ if( G_BuildableRange( ent->client->ps.origin, 900, BA_A_OVERMIND ) )
+ {
+ ent->client->pers.statscounters.timeinbase++;
+ level.alienStatsCounters.timeinbase++;
+ }
+ if( BG_ClassHasAbility( ent->client->ps.stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) )
+ {
+ ent->client->pers.statscounters.dretchbasytime++;
+ level.alienStatsCounters.dretchbasytime++;
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING || ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING)
+ {
+ ent->client->pers.statscounters.jetpackusewallwalkusetime++;
+ level.alienStatsCounters.jetpackusewallwalkusetime++;
+ }
+ }
+ }
+ else if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ ent->client->pers.statscounters.timealive++;
+ level.humanStatsCounters.timealive++;
+ if( G_BuildableRange( ent->client->ps.origin, 900, BA_H_REACTOR ) )
+ {
+ ent->client->pers.statscounters.timeinbase++;
+ level.humanStatsCounters.timeinbase++;
+ }
+ if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) )
+ {
+ if( client->ps.pm_type == PM_JETPACK )
+ {
+ ent->client->pers.statscounters.jetpackusewallwalkusetime++;
+ level.humanStatsCounters.jetpackusewallwalkusetime++;
+ }
+ }
+ }
+
+ // turn off life support when a team admits defeat
+ if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ level.surrenderTeam == PTE_ALIENS )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL,
+ BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ),
+ DAMAGE_NO_ARMOR, MOD_SUICIDE );
+ }
+ else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ level.surrenderTeam == PTE_HUMANS )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE );
+ }
+
+ //my new jetpack code
+ if( mod_jetpackFuel.value >= 10.0f ) {
+ //if we have jetpack and its on
+ if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) {
+ //check if fuels 0 if so deactivate it if not give a 10 second fuel low warning and take JETPACK_USE_RATE from fuel
+ if( client->jetpackfuel <= 0.0f ) {
+ BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats );
+ } else if( client->jetpackfuel < 10.0f && client->jetpackfuel > 0.0f) {
+ client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value;
+ trap_SendServerCommand( client - level.clients, "cp \"^3Fuel ^1Low!!!!!^7\nLand now.\"" );
+ } else {
+ client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value;
+ }
+
+ //if jetpack isnt active regenerate fuel and give a message when its full
+ } else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && !BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) {
+ if( client->jetpackfuel > ( mod_jetpackFuel.value - 10.0f ) && client->jetpackfuel <= mod_jetpackFuel.value ) {
+ client->jetpackfuel = client->jetpackfuel + mod_jetpackRegen.value;
+ trap_SendServerCommand( client - level.clients, "cp \"^3Fuel Status: ^2Full!^7\n\"" );
+ } else if( client->jetpackfuel < mod_jetpackFuel.value ) {
+ //regenerate some fuel
+ client->jetpackfuel = client->jetpackfuel + mod_jetpackRegen.value;
+ }
+ }
+ }
+ }
+
+ while( client->time10000 >= 10000 )
+ {
+ client->time10000 -= 10000;
+
+ if( client->ps.weapon == WP_ALEVEL3_UPG )
+ {
+ int ammo, maxAmmo;
+
+ BG_FindAmmoForWeapon( WP_ALEVEL3_UPG, &maxAmmo, NULL );
+ ammo = client->ps.ammo;
+
+ if( ammo < maxAmmo )
+ {
+ ammo++;
+ client->ps.ammo = ammo;
+ client->ps.clips = 0;
+ }
+ }
+ }
+}
+
+/*
+====================
+ClientIntermissionThink
+====================
+*/
+void ClientIntermissionThink( gclient_t *client )
+{
+ client->ps.eFlags &= ~EF_TALK;
+ client->ps.eFlags &= ~EF_FIRING;
+ client->ps.eFlags &= ~EF_FIRING2;
+
+ // the level will exit when everyone wants to or after timeouts
+
+ // swap and latch button actions
+ client->oldbuttons = client->buttons;
+ client->buttons = client->pers.cmd.buttons;
+ if( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) )
+ client->readyToExit = 1;
+}
+
+
+/*
+================
+ClientEvents
+
+Events will be passed on to the clients for presentation,
+but any server game effects are handled here
+================
+*/
+void ClientEvents( gentity_t *ent, int oldEventSequence )
+{
+ int i;
+ int event;
+ gclient_t *client;
+ int damage;
+ vec3_t dir;
+ vec3_t point, mins;
+ float fallDistance;
+ pClass_t class;
+
+ client = ent->client;
+ class = client->ps.stats[ STAT_PCLASS ];
+
+ if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS )
+ oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS;
+
+ for( i = oldEventSequence; i < client->ps.eventSequence; i++ )
+ {
+ event = client->ps.events[ i & ( MAX_PS_EVENTS - 1 ) ];
+
+ switch( event )
+ {
+ case EV_FALL_MEDIUM:
+ case EV_FALL_FAR:
+ if( ent->s.eType != ET_PLAYER )
+ break; // not in the player model
+
+ fallDistance = ( (float)client->ps.stats[ STAT_FALLDIST ] - MIN_FALL_DISTANCE ) /
+ ( MAX_FALL_DISTANCE - MIN_FALL_DISTANCE );
+
+ if( fallDistance < 0.0f )
+ fallDistance = 0.0f;
+ else if( fallDistance > 1.0f )
+ fallDistance = 1.0f;
+
+ damage = (int)( (float)BG_FindHealthForClass( class ) *
+ BG_FindFallDamageForClass( class ) * fallDistance );
+
+ VectorSet( dir, 0, 0, 1 );
+ BG_FindBBoxForClass( class, mins, NULL, NULL, NULL, NULL );
+ mins[ 0 ] = mins[ 1 ] = 0.0f;
+ VectorAdd( client->ps.origin, mins, point );
+
+ ent->pain_debounce_time = level.time + 200; // no normal pain sound
+ G_Damage( ent, NULL, NULL, dir, point, damage, DAMAGE_NO_LOCDAMAGE, MOD_FALLING );
+ break;
+
+ case EV_FIRE_WEAPON:
+ FireWeapon( ent );
+ break;
+
+ case EV_FIRE_WEAPON2:
+ FireWeapon2( ent );
+ break;
+
+ case EV_FIRE_WEAPON3:
+ FireWeapon3( ent );
+ break;
+
+ case EV_NOAMMO:
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/*
+==============
+SendPendingPredictableEvents
+==============
+*/
+void SendPendingPredictableEvents( playerState_t *ps )
+{
+ gentity_t *t;
+ int event, seq;
+ int extEvent, number;
+
+ // if there are still events pending
+ if( ps->entityEventSequence < ps->eventSequence )
+ {
+ // create a temporary entity for this event which is sent to everyone
+ // except the client who generated the event
+ seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 );
+ event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
+ // set external event to zero before calling BG_PlayerStateToEntityState
+ extEvent = ps->externalEvent;
+ ps->externalEvent = 0;
+ // create temporary entity for event
+ t = G_TempEntity( ps->origin, event );
+ number = t->s.number;
+ BG_PlayerStateToEntityState( ps, &t->s, qtrue );
+ t->s.number = number;
+ t->s.eType = ET_EVENTS + event;
+ t->s.eFlags |= EF_PLAYER_EVENT;
+ t->s.otherEntityNum = ps->clientNum;
+ // send to everyone except the client who generated the event
+ t->r.svFlags |= SVF_NOTSINGLECLIENT;
+ t->r.singleClient = ps->clientNum;
+ // set back external event
+ ps->externalEvent = extEvent;
+ }
+}
+
+/*
+==============
+ G_UnlaggedStore
+
+ Called on every server frame. Stores position data for the client at that
+ into client->unlaggedHist[] and the time into level.unlaggedTimes[].
+ This data is used by G_UnlaggedCalc()
+==============
+*/
+void G_UnlaggedStore( void )
+{
+ int i = 0;
+ gentity_t *ent;
+ unlagged_t *save;
+
+ if( !g_unlagged.integer )
+ return;
+ level.unlaggedIndex++;
+ if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS )
+ level.unlaggedIndex = 0;
+
+ level.unlaggedTimes[ level.unlaggedIndex ] = level.time;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ ent = &g_entities[ i ];
+ save = &ent->client->unlaggedHist[ level.unlaggedIndex ];
+ save->used = qfalse;
+ if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) )
+ continue;
+ if( ent->client->pers.connected != CON_CONNECTED )
+ continue;
+ VectorCopy( ent->r.mins, save->mins );
+ VectorCopy( ent->r.maxs, save->maxs );
+ VectorCopy( ent->s.pos.trBase, save->origin );
+ save->used = qtrue;
+ }
+}
+
+/*
+==============
+ G_UnlaggedClear
+
+ Mark all unlaggedHist[] markers for this client invalid. Useful for
+ preventing teleporting and death.
+==============
+*/
+void G_UnlaggedClear( gentity_t *ent )
+{
+ int i;
+
+ for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ )
+ ent->client->unlaggedHist[ i ].used = qfalse;
+}
+
+/*
+==============
+ G_UnlaggedCalc
+
+ Loops through all active clients and calculates their predicted position
+ for time then stores it in client->unlaggedCalc
+==============
+*/
+void G_UnlaggedCalc( int time, gentity_t *rewindEnt )
+{
+ int i = 0;
+ gentity_t *ent;
+ int startIndex = level.unlaggedIndex;
+ int stopIndex = -1;
+ int frameMsec = 0;
+ float lerp = 0.5f;
+
+ if( !g_unlagged.integer )
+ return;
+
+ // clear any calculated values from a previous run
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ ent = &g_entities[ i ];
+ ent->client->unlaggedCalc.used = qfalse;
+ }
+
+ for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ )
+ {
+ if( level.unlaggedTimes[ startIndex ] <= time )
+ break;
+ stopIndex = startIndex;
+ if( --startIndex < 0 )
+ startIndex = MAX_UNLAGGED_MARKERS - 1;
+ }
+ if( i == MAX_UNLAGGED_MARKERS )
+ {
+ // if we searched all markers and the oldest one still isn't old enough
+ // just use the oldest marker with no lerping
+ lerp = 0.0f;
+ }
+
+ // client is on the current frame, no need for unlagged
+ if( stopIndex == -1 )
+ return;
+
+ // lerp between two markers
+ frameMsec = level.unlaggedTimes[ stopIndex ] -
+ level.unlaggedTimes[ startIndex ];
+ if( frameMsec > 0 )
+ {
+ lerp = ( float )( time - level.unlaggedTimes[ startIndex ] )
+ / ( float )frameMsec;
+ }
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ ent = &g_entities[ i ];
+ if( ent == rewindEnt )
+ continue;
+ if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) )
+ continue;
+ if( ent->client->pers.connected != CON_CONNECTED )
+ continue;
+ if( !ent->client->unlaggedHist[ startIndex ].used )
+ continue;
+ if( !ent->client->unlaggedHist[ stopIndex ].used )
+ continue;
+
+ // between two unlagged markers
+ VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins,
+ ent->client->unlaggedHist[ stopIndex ].mins,
+ ent->client->unlaggedCalc.mins );
+ VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs,
+ ent->client->unlaggedHist[ stopIndex ].maxs,
+ ent->client->unlaggedCalc.maxs );
+ VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin,
+ ent->client->unlaggedHist[ stopIndex ].origin,
+ ent->client->unlaggedCalc.origin );
+
+ ent->client->unlaggedCalc.used = qtrue;
+ }
+}
+
+/*
+==============
+ G_UnlaggedOff
+
+ Reverses the changes made to all active clients by G_UnlaggedOn()
+==============
+*/
+void G_UnlaggedOff( void )
+{
+ int i = 0;
+ gentity_t *ent;
+
+ if( !g_unlagged.integer )
+ return;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ ent = &g_entities[ i ];
+ if( !ent->client->unlaggedBackup.used )
+ continue;
+ VectorCopy( ent->client->unlaggedBackup.mins, ent->r.mins );
+ VectorCopy( ent->client->unlaggedBackup.maxs, ent->r.maxs );
+ VectorCopy( ent->client->unlaggedBackup.origin, ent->r.currentOrigin );
+ ent->client->unlaggedBackup.used = qfalse;
+ trap_LinkEntity( ent );
+ }
+}
+
+/*
+==============
+ G_UnlaggedOn
+
+ Called after G_UnlaggedCalc() to apply the calculated values to all active
+ clients. Once finished tracing, G_UnlaggedOff() must be called to restore
+ the clients' position data
+
+ As an optimization, all clients that have an unlagged position that is
+ not touchable at "range" from "muzzle" will be ignored. This is required
+ to prevent a huge amount of trap_LinkEntity() calls per user cmd.
+==============
+*/
+
+void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range )
+{
+ int i = 0;
+ gentity_t *ent;
+ unlagged_t *calc;
+
+ if( !g_unlagged.integer )
+ return;
+
+ if( !attacker->client->pers.useUnlagged )
+ return;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ ent = &g_entities[ i ];
+ calc = &ent->client->unlaggedCalc;
+
+ if( !calc->used )
+ continue;
+ if( ent->client->unlaggedBackup.used )
+ continue;
+ if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) )
+ continue;
+ if( VectorCompare( ent->r.currentOrigin, calc->origin ) )
+ continue;
+ if( muzzle )
+ {
+ float r1 = Distance( calc->origin, calc->maxs );
+ float r2 = Distance( calc->origin, calc->mins );
+ float maxRadius = ( r1 > r2 ) ? r1 : r2;
+
+ if( Distance( muzzle, calc->origin ) > range + maxRadius )
+ continue;
+ }
+
+ // create a backup of the real positions
+ VectorCopy( ent->r.mins, ent->client->unlaggedBackup.mins );
+ VectorCopy( ent->r.maxs, ent->client->unlaggedBackup.maxs );
+ VectorCopy( ent->r.currentOrigin, ent->client->unlaggedBackup.origin );
+ ent->client->unlaggedBackup.used = qtrue;
+
+ // move the client to the calculated unlagged position
+ VectorCopy( calc->mins, ent->r.mins );
+ VectorCopy( calc->maxs, ent->r.maxs );
+ VectorCopy( calc->origin, ent->r.currentOrigin );
+ trap_LinkEntity( ent );
+ }
+}
+/*
+==============
+ G_UnlaggedDetectCollisions
+
+ cgame prediction will predict a client's own position all the way up to
+ the current time, but only updates other player's positions up to the
+ postition sent in the most recent snapshot.
+
+ This allows player X to essentially "move through" the position of player Y
+ when player X's cmd is processed with Pmove() on the server. This is because
+ player Y was clipping player X's Pmove() on his client, but when the same
+ cmd is processed with Pmove on the server it is not clipped.
+
+ Long story short (too late): don't use unlagged positions for players who
+ were blocking this player X's client-side Pmove(). This makes the assumption
+ that if player X's movement was blocked in the client he's going to still
+ be up against player Y when the Pmove() is run on the server with the
+ same cmd.
+
+ NOTE: this must be called after Pmove() and G_UnlaggedCalc()
+==============
+*/
+static void G_UnlaggedDetectCollisions( gentity_t *ent )
+{
+ unlagged_t *calc;
+ trace_t tr;
+ float r1, r2;
+ float range;
+
+ if( !g_unlagged.integer )
+ return;
+ if( !ent->client->pers.useUnlagged )
+ return;
+
+ calc = &ent->client->unlaggedCalc;
+
+ // if the client isn't moving, this is not necessary
+ if( VectorCompare( ent->client->oldOrigin, ent->client->ps.origin ) )
+ return;
+
+ range = Distance( ent->client->oldOrigin, ent->client->ps.origin );
+
+ // increase the range by the player's largest possible radius since it's
+ // the players bounding box that collides, not their origin
+ r1 = Distance( calc->origin, calc->mins );
+ r2 = Distance( calc->origin, calc->maxs );
+ range += ( r1 > r2 ) ? r1 : r2;
+
+ G_UnlaggedOn( ent, ent->client->oldOrigin, range );
+
+ trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs,
+ ent->client->ps.origin, ent->s.number, MASK_PLAYERSOLID );
+ if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS )
+ g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse;
+
+ G_UnlaggedOff( );
+}
+
+/*
+==============
+ClientThink
+
+This will be called once for each client frame, which will
+usually be a couple times for each server frame on fast clients.
+
+If "g_synchronousClients 1" is set, this will be called exactly
+once for each server frame, which makes for smooth demo recording.
+==============
+*/
+void ClientThink_real( gentity_t *ent )
+{
+ gclient_t *client;
+ pmove_t pm;
+ int oldEventSequence;
+ int msec;
+ usercmd_t *ucmd;
+ int real_pm_type;
+
+ client = ent->client;
+
+ // don't think if the client is not yet connected (and thus not yet spawned in)
+ if( client->pers.connected != CON_CONNECTED )
+ return;
+
+ // mark the time, so the connection sprite can be removed
+ ucmd = &ent->client->pers.cmd;
+
+ if( client->pers.paused )
+ ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = ucmd->buttons = 0;
+
+ // sanity check the command time to prevent speedup cheating
+ if( ucmd->serverTime > level.time + 200 )
+ {
+ ucmd->serverTime = level.time + 200;
+// G_Printf("serverTime <<<<<\n" );
+ }
+
+ if( ucmd->serverTime < level.time - 1000 )
+ {
+ ucmd->serverTime = level.time - 1000;
+// G_Printf("serverTime >>>>>\n" );
+ }
+
+ // ucmd->serverTime is a client predicted value, but it works for making a
+ // replacement for client->ps.ping when in SPECTATOR_FOLLOW
+ client->pers.ping = level.time - ucmd->serverTime;
+
+ // account for the one frame of delay on client side
+ client->pers.ping -= level.time - level.previousTime;
+
+ // account for the time that's elapsed since the last ClientEndFrame()
+ client->pers.ping += trap_Milliseconds( ) - level.frameMsec;
+
+ if( client->pers.ping < 0 )
+ client->pers.ping = 0;
+
+ msec = ucmd->serverTime - client->ps.commandTime;
+ // following others may result in bad times, but we still want
+ // to check for follow toggles
+ if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW )
+ return;
+
+ if( msec > 200 )
+ msec = 200;
+
+ client->unlaggedTime = ucmd->serverTime;
+
+ if( pmove_msec.integer < 8 )
+ trap_Cvar_Set( "pmove_msec", "8" );
+ else if( pmove_msec.integer > 33 )
+ trap_Cvar_Set( "pmove_msec", "33" );
+
+ if( pmove_fixed.integer || client->pers.pmoveFixed )
+ {
+ ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer;
+ //if (ucmd->serverTime - client->ps.commandTime <= 0)
+ // return;
+ }
+
+ //
+ // check for exiting intermission
+ //
+ if( level.intermissiontime )
+ {
+ if( level.mapRotationVoteTime )
+ {
+ SpectatorThink( ent, ucmd );
+ return;
+ }
+
+ ClientIntermissionThink( client );
+ return;
+ }
+
+ if( client->pers.teamSelection != PTE_NONE && client->pers.joinedATeam )
+ G_UpdatePTRConnection( client );
+
+ // spectators don't do much
+ if( client->sess.sessionTeam == TEAM_SPECTATOR )
+ {
+ if( client->sess.spectatorState == SPECTATOR_SCOREBOARD )
+ return;
+
+ SpectatorThink( ent, ucmd );
+ return;
+ }
+
+ // check for inactivity timer, but never drop the local client of a non-dedicated server
+ if( !ClientInactivityTimer( client ) )
+ return;
+
+ // calculate where ent is currently seeing all the other active clients
+ G_UnlaggedCalc( ent->client->unlaggedTime, ent );
+
+ if( client->noclip )
+ client->ps.pm_type = PM_NOCLIP;
+ else if( client->ps.stats[ STAT_HEALTH ] <= 0 )
+ client->ps.pm_type = PM_DEAD;
+ else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING ||
+ client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ client->ps.pm_type = PM_FREEZE;
+ else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ||
+ client->ps.stats[ STAT_STATE ] & SS_GRABBED )
+ client->ps.pm_type = PM_GRABBED;
+ else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
+ client->ps.pm_type = PM_JETPACK;
+ else
+ client->ps.pm_type = PM_NORMAL;
+
+ // paused
+ real_pm_type = client->ps.pm_type;
+ if ( level.paused ) client->ps.pm_type = PM_SPECTATOR;
+
+ if( client->ps.stats[ STAT_STATE ] & SS_GRABBED &&
+ client->grabExpiryTime < level.time )
+ client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED;
+
+ if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED &&
+ client->lastLockTime + LOCKBLOB_LOCKTIME < level.time )
+ client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED;
+
+ if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED &&
+ client->lastSlowTime + ABUILDER_BLOB_TIME < level.time )
+ client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED;
+
+ client->ps.stats[ STAT_BOOSTTIME ] = level.time - client->lastBoostedTime;
+
+ if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED &&
+ client->lastBoostedTime + BOOST_TIME < level.time )
+ client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED;
+
+ if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
+ client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time )
+ client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
+
+ if( client->ps.stats[ STAT_STATE ] & SS_POISONED &&
+ client->lastPoisonTime + ALIEN_POISON_TIME < level.time )
+ client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
+
+ client->ps.gravity = g_gravity.value;
+
+ if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) &&
+ BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) )
+ {
+ //if currently using a medkit or have no need for a medkit now
+ if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ||
+ ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] &&
+ !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) )
+ {
+ BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats );
+ }
+ else if( client->ps.stats[ STAT_HEALTH ] > 0 && !level.paused )
+ {
+ //remove anti toxin
+ BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats );
+ BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats );
+
+ client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
+ client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME;
+
+ client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE;
+ client->lastMedKitTime = level.time;
+ client->medKitHealthToRestore =
+ client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ];
+ client->medKitIncrementTime = level.time +
+ ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED );
+
+ G_AddEvent( ent, EV_MEDKIT_USED, 0 );
+ }
+ }
+
+ if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) &&
+ BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) )
+ {
+ int lastWeapon = ent->s.weapon;
+
+ //remove grenade
+ BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats );
+ BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats );
+
+ //M-M-M-M-MONSTER HACK
+ ent->s.weapon = WP_GRENADE;
+ FireWeapon( ent );
+ ent->s.weapon = lastWeapon;
+ }
+
+ // set speed
+ client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
+
+ if( client->pers.paused )
+ client->ps.speed = 0;
+
+ if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time )
+ client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED;
+
+ //randomly disable the jet pack if damaged
+ if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) &&
+ BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
+ {
+ if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time )
+ {
+ if( random( ) > JETPACK_DISABLE_CHANCE )
+ client->ps.pm_type = PM_NORMAL;
+ }
+
+ //switch jetpack off if no reactor
+ if( !level.reactorPresent )
+ BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats );
+ }
+
+ // set up for pmove
+ oldEventSequence = client->ps.eventSequence;
+
+ memset( &pm, 0, sizeof( pm ) );
+
+ if( !( ucmd->buttons & BUTTON_TALK ) && !( client->ps.pm_flags & PMF_RESPAWNED ) )
+ {
+ switch( client->ps.weapon )
+ {
+ case WP_ALEVEL0:
+ if( client->ps.weaponTime <= 0 )
+ pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent );
+ break;
+
+ case WP_ALEVEL1:
+ case WP_ALEVEL1_UPG:
+ CheckGrabAttack( ent );
+ break;
+
+ case WP_ALEVEL3:
+ case WP_ALEVEL3_UPG:
+ if( client->ps.weaponTime <= 0 )
+ pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( ent->flags & FL_FORCE_GESTURE )
+ {
+ ent->flags &= ~FL_FORCE_GESTURE;
+ ent->client->pers.cmd.buttons |= BUTTON_GESTURE;
+ }
+
+ pm.ps = &client->ps;
+ pm.pmext = &client->pmext;
+ pm.cmd = *ucmd;
+
+ if( pm.ps->pm_type == PM_DEAD )
+ pm.tracemask = MASK_PLAYERSOLID; // & ~CONTENTS_BODY;
+
+ if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING ||
+ pm.ps->stats[ STAT_STATE ] & SS_HOVELING )
+ pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
+ else
+ pm.tracemask = MASK_PLAYERSOLID;
+
+ pm.trace = trap_Trace;
+ pm.pointcontents = trap_PointContents;
+ pm.debugLevel = g_debugMove.integer;
+ pm.noFootsteps = 0;
+
+ pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed;
+ pm.pmove_msec = pmove_msec.integer;
+
+ VectorCopy( client->ps.origin, client->oldOrigin );
+
+ // moved from after Pmove -- potentially the cause of
+ // future triggering bugs
+ if( !ent->client->noclip )
+ G_TouchTriggers( ent );
+
+ Pmove( &pm );
+
+ G_UnlaggedDetectCollisions( ent );
+
+ // save results of pmove
+ if( ent->client->ps.eventSequence != oldEventSequence )
+ ent->eventTime = level.time;
+
+ if ( level.paused ) client->ps.pm_type = real_pm_type;
+
+ if( g_smoothClients.integer )
+ BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
+ else
+ BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
+
+ SendPendingPredictableEvents( &ent->client->ps );
+
+ if( !( ent->client->ps.eFlags & EF_FIRING ) )
+ client->fireHeld = qfalse; // for grapple
+ if( !( ent->client->ps.eFlags & EF_FIRING2 ) )
+ client->fire2Held = qfalse;
+
+ // use the snapped origin for linking so it matches client predicted versions
+ VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
+
+ VectorCopy( pm.mins, ent->r.mins );
+ VectorCopy( pm.maxs, ent->r.maxs );
+
+ ent->waterlevel = pm.waterlevel;
+ ent->watertype = pm.watertype;
+
+ // touch other objects
+ ClientImpacts( ent, &pm );
+
+ // execute client events
+ ClientEvents( ent, oldEventSequence );
+
+ // link entity now, after any personal teleporters have been used
+ trap_LinkEntity( ent );
+
+ // NOTE: now copy the exact origin over otherwise clients can be snapped into solid
+ VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
+ VectorCopy( ent->client->ps.origin, ent->s.origin );
+
+ // save results of triggers and client events
+ if( ent->client->ps.eventSequence != oldEventSequence )
+ ent->eventTime = level.time;
+
+ // Don't think anymore if dead
+ if( client->ps.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ // swap and latch button actions
+ client->oldbuttons = client->buttons;
+ client->buttons = ucmd->buttons;
+ client->latched_buttons |= client->buttons & ~client->oldbuttons;
+
+ if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) &&
+ client->ps.stats[ STAT_HEALTH ] > 0 )
+ {
+ trace_t trace;
+ vec3_t view, point;
+ gentity_t *traceEnt;
+
+ if( client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ {
+ gentity_t *hovel = client->hovel;
+
+ //only let the player out if there is room
+ if( !AHovel_Blocked( hovel, ent, qtrue ) )
+ {
+ //prevent lerping
+ client->ps.eFlags ^= EF_TELEPORT_BIT;
+ client->ps.eFlags &= ~EF_NODRAW;
+ G_UnlaggedClear( ent );
+
+ //client leaves hovel
+ client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
+
+ //hovel is empty
+ G_SetBuildableAnim( hovel, BANIM_ATTACK2, qfalse );
+ hovel->active = qfalse;
+ }
+ else
+ {
+ //exit is blocked
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED );
+ }
+ }
+ else
+ {
+#define USE_OBJECT_RANGE 64
+
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+
+ //TA: look for object infront of player
+ AngleVectors( client->ps.viewangles, view, NULL, NULL );
+ VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point );
+ trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT );
+
+ traceEnt = &g_entities[ trace.entityNum ];
+
+ if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use )
+ traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context
+ else
+ {
+ //no entity in front of player - do a small area search
+
+ VectorAdd( client->ps.origin, range, maxs );
+ VectorSubtract( client->ps.origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ traceEnt = &g_entities[ entityList[ i ] ];
+
+ if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use )
+ {
+ traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context
+ break;
+ }
+ }
+
+ if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if( BG_UpgradeClassAvailable( &client->ps ) )
+ {
+ //no nearby objects and alien - show class menu
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST );
+ }
+ else
+ {
+ //flash frags
+ G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 );
+ }
+ }
+ }
+ }
+ }
+
+ if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu )
+ {
+ G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY );
+
+ client->retriggerArmouryMenu = 0;
+ }
+
+ // Give clients some credit periodically
+ if( ent->client->lastKillTime + FREEKILL_PERIOD < level.time )
+ {
+ if( !g_suddenDeath.integer ) {
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue );
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue );
+ }
+ ent->client->lastKillTime = level.time;
+ }
+
+ // perform once-a-second actions
+ ClientTimerActions( ent, msec );
+
+ if( ent->suicideTime > 0 && ent->suicideTime < level.time )
+ {
+ ent->flags &= ~FL_GODMODE;
+ ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0;
+ player_die( ent, ent, ent, 100000, MOD_SUICIDE );
+
+ ent->suicideTime = 0;
+ }
+}
+
+/*
+==================
+ClientThink
+
+A new command has arrived from the client
+==================
+*/
+void ClientThink( int clientNum )
+{
+ gentity_t *ent;
+
+ ent = g_entities + clientNum;
+ trap_GetUsercmd( clientNum, &ent->client->pers.cmd );
+
+ // mark the time we got info, so we can display the
+ // phone jack if they don't get any for a while
+ ent->client->lastCmdTime = level.time;
+
+ if( !g_synchronousClients.integer )
+ ClientThink_real( ent );
+}
+
+
+void G_RunClient( gentity_t *ent )
+{
+ if( !g_synchronousClients.integer )
+ return;
+
+ ent->client->pers.cmd.serverTime = level.time;
+ ClientThink_real( ent );
+}
+
+
+/*
+==================
+SpectatorClientEndFrame
+
+==================
+*/
+void SpectatorClientEndFrame( gentity_t *ent )
+{
+ gclient_t *cl;
+ int clientNum, flags;
+ int score, ping;
+ vec3_t spawn_origin, spawn_angles;
+
+ // if we are doing a chase cam or a remote view, grab the latest info
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ {
+ clientNum = ent->client->sess.spectatorClient;
+
+ if( clientNum >= 0 )
+ {
+ cl = &level.clients[ clientNum ];
+
+ if( cl->pers.connected == CON_CONNECTED )
+ {
+
+ if( cl -> sess.spectatorState != SPECTATOR_FOLLOW )
+ {
+ flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) |
+ ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) );
+ score = ent->client->ps.persistant[ PERS_SCORE ];
+ ping = ent->client->ps.ping;
+ ent->client->ps = cl->ps;
+ ent->client->ps.persistant[ PERS_SCORE ] = score;
+ ent->client->ps.ping = ping;
+ ent->client->ps.eFlags = flags;
+ ent->client->ps.pm_flags |= PMF_FOLLOW;
+ ent->client->ps.pm_flags &= ~PMF_QUEUED;
+ }
+ else //we are stickyspec-spectating someone who is spectating someone else
+ {
+ ent->client->ps.clientNum = (g_entities + clientNum)->s.number;
+ ent->client->ps.commandTime = cl->ps.commandTime;
+ ent->client->ps.weapon = 0;
+ ent->client->ps.pm_flags |= PMF_FOLLOW;
+ ent->client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
+
+ if( cl->pers.teamSelection == PTE_ALIENS )
+ G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
+ else if( cl->pers.teamSelection == PTE_HUMANS )
+ G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
+
+ G_SetOrigin( ent, spawn_origin );
+ VectorCopy( spawn_origin, ent->client->ps.origin );
+ G_SetClientViewAngle( ent, spawn_angles );
+ }
+ }
+ }
+ }
+}
+
+/*
+==============
+ClientEndFrame
+
+Called at the end of each server frame for each connected client
+A fast client will have multiple ClientThink for each ClientEdFrame,
+while a slow client may have multiple ClientEndFrame between ClientThink.
+==============
+*/
+void ClientEndFrame( gentity_t *ent )
+{
+ clientPersistant_t *pers;
+
+ if( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
+ {
+ SpectatorClientEndFrame( ent );
+ return;
+ }
+
+ pers = &ent->client->pers;
+
+ // save a copy of certain playerState values in case of SPECTATOR_FOLLOW
+ pers->score = ent->client->ps.persistant[ PERS_SCORE ];
+ pers->credit = ent->client->ps.persistant[ PERS_CREDIT ];
+
+ //
+ // If the end of unit layout is displayed, don't give
+ // the player any normal movement attributes
+ //
+ if( level.intermissiontime )
+ return;
+
+ // burn from lava, etc
+ P_WorldEffects( ent );
+
+ // apply all the damage taken this frame
+ P_DamageFeedback( ent );
+
+ // add the EF_CONNECTION flag if we haven't gotten commands recently
+ if( level.time - ent->client->lastCmdTime > 1000 )
+ ent->s.eFlags |= EF_CONNECTION;
+ else
+ ent->s.eFlags &= ~EF_CONNECTION;
+
+ ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health...
+
+ // respawn if dead
+ if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 && level.time >= ent->client->respawnTime )
+ respawn( ent );
+
+ G_SetClientSound( ent );
+
+ // set the latest infor
+ if( g_smoothClients.integer )
+ BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
+ else
+ BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
+
+ SendPendingPredictableEvents( &ent->client->ps );
+}
+
+
diff --git a/src/game/g_admin.c b/src/game/g_admin.c
new file mode 100644
index 0000000..1cc55b4
--- /dev/null
+++ b/src/game/g_admin.c
@@ -0,0 +1,7530 @@
+/*
+===========================================================================
+Copyright (C) 2004-2006 Tony J. White
+
+This file is part of Tremulous.
+
+This shrubbot implementation is the original work of Tony J. White.
+
+Contains contributions from Wesley van Beelen, Chris Bajumpaa, Josh Menke,
+and Travis Maurer.
+
+The functionality of this code mimics the behaviour of the currently
+inactive project shrubet (http://www.etstats.com/shrubet/index.php?ver=2)
+by Ryan Mannion. However, shrubet was a closed-source project and
+none of it's code has been copied, only it's functionality.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+// big ugly global buffer for use with buffered printing of long outputs
+static char g_bfb[ 32000 ];
+
+// note: list ordered alphabetically
+g_admin_cmd_t g_admin_cmds[ ] =
+ {
+ {"adjustban", G_admin_adjustban, "ban",
+ "change the duration or reason of a ban. time is specified as numbers "
+ "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes),"
+ " or seconds if no units are specified. if the duration is"
+ " preceded by a + or -, the ban duration will be extended or shortened by"
+ " the specified amount",
+ "[^3ban#^7] (^5duration^7) (^5reason^7)"
+ },
+
+ {"adminlog", G_admin_adminlog, "adminlog",
+ "list recent admin activity",
+ "(^5start id#|name|!command|-skip#^7) (^5search skip#^7)"
+ },
+
+ {"admintest", G_admin_admintest, "admintest",
+ "display your current admin level",
+ ""
+ },
+
+ {"allowbuild", G_admin_denybuild, "denybuild",
+ "restore a player's ability to build",
+ "[^3name|slot#^7]"
+ },
+
+ {"allowweapon", G_admin_denyweapon, "denyweapon",
+ "restore a player's ability to use a weapon or class",
+ "[^3name|slot#^7] [^3class|weapon|all^7]"
+ },
+
+ {"allready", G_admin_allready, "allready",
+ "makes everyone ready in intermission",
+ ""
+ },
+
+ {"ban", G_admin_ban, "ban",
+ "ban a player by IP and GUID with an optional expiration time and reason."
+ " time is specified as numbers followed by units 'w' (weeks), 'd' "
+ "(days), 'h' (hours) or 'm' (minutes), or seconds if no units are "
+ "specified",
+ "[^3name|slot#|IP^7] (^5time^7) (^5reason^7)"
+ },
+
+ {"buildlog", G_admin_buildlog, "buildlog",
+ "display a list of recent builds and deconstructs, optionally specifying"
+ " a team",
+ "(^5xnum^7) (^5#skip^7) (^5-name|num^7) (^5a|h^7)"
+ "\n ^3Example:^7 '!buildlog #10 h' skips 10 events, then shows the previous 10 events affecting human buildables"
+ },
+
+ {"cancelvote", G_admin_cancelvote, "cancelvote",
+ "cancel a vote taking place",
+ ""
+ },
+
+ {"cp", G_admin_cp, "cp",
+ "display a CP message to users, optionally specifying team(s) to send to",
+ "(-AHS) [^3message^7]"
+ },
+
+ {"decon", G_admin_decon, "decon",
+ "Reverts a decon previously made and ban the user for the time specified in g_deconBanTime",
+ "[^3name|slot#^7]"
+ },
+
+ {"demo", G_admin_demo, "demo",
+ "turn admin chat off for the caller so it does not appear in demos. "
+ "this is a toggle use !demo again to turn warnings back on",
+ ""
+ },
+
+ {"denybuild", G_admin_denybuild, "denybuild",
+ "take away a player's ability to build",
+ "[^3name|slot#^7]"
+ },
+
+ {"designate", G_admin_designate, "designate",
+ "give the player designated builder privileges",
+ "[^3name|slot#^7]"
+ },
+
+ {"devmap", G_admin_devmap, "devmap",
+ "load a map with cheats (and optionally force layout)",
+ "[^3mapname^7] (^5layout^7)"
+ },
+
+ {"denyweapon", G_admin_denyweapon, "denyweapon",
+ "take away a player's ability to use a weapon or class",
+ "[^3name|slot#^7] [^3class|weapon^7]"
+ },
+
+ {"drop", G_admin_drop, "drop",
+ "kick a client from the server without log",
+ "[^3name|slot#^7] [^3message^7]"
+ },
+
+ {"flag", G_admin_flag, "flag",
+ "add an admin flag to a player, prefix flag with '-' to disallow the flag. "
+ "console can use this command on admin levels by prefacing a '*' to the admin level value.",
+ "[^3name|slot#|admin#|*adminlevel^7] (^5+^7|^5-^7)[^3flag^7]"
+ },
+
+ {"flaglist", G_admin_flaglist, "flag",
+ "list all flags understood by this server",
+ ""
+ },
+
+ {"help", G_admin_help, "help",
+ "display commands available to you or help on a specific command",
+ "(^5command^7)"
+ },
+
+ {"info", G_admin_info, "info",
+ "display the contents of server info files",
+ "(^5subject^7)"
+ },
+
+ {"invisible", G_admin_invisible, "invisible",
+ "hides a player so they cannot be seen in playerlists",
+ ""
+ },
+
+ {"kick", G_admin_kick, "kick",
+ "kick a player with an optional reason",
+ "[^3name|slot#^7] (^5reason^7)"
+ },
+
+ {"L0", G_admin_L0, "l0",
+ "Sets a level 1 to level 0",
+ "[^3name|slot#^7]"
+ },
+
+ {"L1", G_admin_L1, "l1",
+ "Sets a level 0 to level 1",
+ "[^3name|slot#^7]"
+ },
+
+ {"layoutsave", G_admin_layoutsave, "layoutsave",
+ "save a map layout",
+ "[^3mapname^7]"
+ },
+
+ {"listadmins", G_admin_listadmins, "listadmins",
+ "display a list of all server admins and their levels",
+ "(^5name|start admin#^7) (^5minimum level to display^7)"
+ },
+
+ {"listlayouts", G_admin_listlayouts, "listlayouts",
+ "display a list of all available layouts for a map",
+ "(^5mapname^7)"
+ },
+
+ {"listplayers", G_admin_listplayers, "listplayers",
+ "display a list of players, their client numbers and their levels",
+ ""
+ },
+
+ {"listmaps", G_admin_listmaps, "listmaps",
+ "display a list of available maps on the server",
+ "(^5map name^7)"
+ },
+
+ {"lock", G_admin_lock, "lock",
+ "lock a team to prevent anyone from joining it",
+ "[^3a|h^7]"
+ },
+
+ {"map", G_admin_map, "map",
+ "load a map (and optionally force layout)",
+ "[^3mapname^7] (^5layout^7)"
+ },
+
+ {"maplog", G_admin_maplog, "maplog",
+ "show recently played maps",
+ ""
+ },
+
+ {"mute", G_admin_mute, "mute",
+ "mute a player",
+ "[^3name|slot#^7] (Duration)"
+ },
+
+ {"namelog", G_admin_namelog, "namelog",
+ "display a list of names used by recently connected players",
+ "(^5name^7)"
+ },
+
+ {"nextmap", G_admin_nextmap, "nextmap",
+ "go to the next map in the cycle",
+ ""
+ },
+
+ {"nobuild", G_admin_nobuild, "nobuild",
+ "set nobuild markers to prevent players from building in an area",
+ "(^5area^7) (^5height^7)"
+ },
+
+ {"passvote", G_admin_passvote, "passvote",
+ "pass a vote currently taking place",
+ ""
+ },
+
+ {"pause", G_admin_pause, "pause",
+ "prevent a player from interacting with the game."
+ " * will pause all players, using no argument will pause game clock",
+ "(^5name|slot|*^7)"
+ },
+
+
+ {"putteam", G_admin_putteam, "putteam",
+ "move a player to a specified team",
+ "[^3name|slot#^7] [^3h|a|s^7] (^3duration^7)"
+ },
+
+ {"readconfig", G_admin_readconfig, "readconfig",
+ "reloads the admin config file and refreshes permission flags",
+ ""
+ },
+
+ {"register", G_admin_register, "register",
+ "Registers your name to protect it from being used by others or updates your admin name to your current name.",
+ ""
+ },
+
+ {"rename", G_admin_rename, "rename",
+ "rename a player",
+ "[^3name|slot#^7] [^3new name^7]"
+ },
+
+ {"restart", G_admin_restart, "restart",
+ "restart the current map (optionally using named layout or keeping/switching teams)",
+ "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)"
+ },
+
+ {"revert", G_admin_revert, "revert",
+ "revert one or more buildlog events, optionally of only one team",
+ "(^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)"
+ "\n ^3Example:^7 '!revert x5 h' reverts the last 5 events affecting human buildables"
+ },
+
+ {"rotation", G_admin_listrotation, "rotation",
+ "display a list of maps that are in the active map rotation",
+ ""
+ },
+
+ {"seen", G_admin_seen, "seen",
+ "find the last time a player was on the server",
+ "[^3name|admin#^7]"
+ },
+
+ {"setlevel", G_admin_setlevel, "setlevel",
+ "sets the admin level of a player",
+ "[^3name|slot#|admin#^7] [^3level^7]"
+ },
+
+ {"showbans", G_admin_showbans, "showbans",
+ "display a (partial) list of active bans",
+ "(^5start at ban#^7) (^5name|IP|'-subnet'^7)"
+ },
+
+ {"slap", G_admin_slap, "slap",
+ "Do damage to a player, and send them flying",
+ "[^3name|slot^7] (damage)"
+ },
+
+ {"spec999", G_admin_spec999, "spec999",
+ "move 999 pingers to the spectator team",
+ ""
+ },
+
+ {"specme", G_admin_putmespec, "specme",
+ "moves you to the spectators",
+ ""
+ },
+
+ {"subnetban", G_admin_subnetban, "subnetban",
+ "Add or change a subnet mask on a ban",
+ "[^3ban#^7] [^5CIDR mask^7]"
+ "\n ^3Example:^7 '!subnetban 10 16' changes ban #10 to be a ban on XXX.XXX.*.*"
+ "\n ^3Example:^7 '!subnetban 10 24' changes ban #10 to be a ban on XXX.XXX.XXX.*"
+ "\n ^3Example:^7 '!subnetban 10 32' changes ban #10 to be a regular (non-subnet) ban"
+ "\n ^1WARNING:^7 Use of this command may make your admin.dat incompatible with other game.qvms"
+ },
+
+ {"suspendban", G_admin_suspendban, "ban",
+ "suspend a ban for a length of time. time is specified as numbers "
+ "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes),"
+ " or seconds if no units are specified",
+ "[^5ban #^7] [^5length^7]"
+ },
+
+ {"time", G_admin_time, "time",
+ "show the current local server time",
+ ""
+ },
+
+ {"unban", G_admin_unban, "ban",
+ "unbans a player specified by the slot as seen in showbans",
+ "[^3ban#^7]"
+ },
+
+ {"undesignate", G_admin_designate, "designate",
+ "revoke designated builder privileges",
+ "[^3name|slot#^7]"
+ },
+
+ {"unflag", G_admin_flag, "flag",
+ "clears an admin flag from a player. "
+ "console can use this command on admin levels by prefacing a '*' to the admin level value.",
+ "[^3name|slot#|admin#|*adminlevel^7] (^5+^7|^5-^7)[^3flag^7]"
+ },
+
+ {"unlock", G_admin_unlock, "lock",
+ "unlock a locked team",
+ "[^3a|h^7]"
+ },
+
+ {"unmute", G_admin_mute, "mute",
+ "unmute a muted player",
+ "[^3name|slot#^7]"
+ },
+
+ {"unpause", G_admin_pause, "pause",
+ "allow a player to interact with the game."
+ " * will unpause all players, using no argument will unpause game clock",
+ "(^5name|slot|*^7)"
+ },
+
+ {
+ "warn", G_admin_warn, "warn",
+ "Warn a player to cease or face admin intervention",
+ "[^3name|slot#^7] [reason]"
+ },
+
+ {"setdevmode", G_admin_setdevmode, "setdevmode",
+ "switch developer mode on or off",
+ "[^3on|off^7]"
+ },
+
+ {"hstage", G_admin_hstage, "stage",
+ "change the stage for humans",
+ "[^3#^7]"
+ },
+
+ {"astage", G_admin_astage, "stage",
+ "change the stage for aliens",
+ "[^3#^7]"
+ }
+ };
+
+static int adminNumCmds = sizeof( g_admin_cmds ) / sizeof( g_admin_cmds[ 0 ] );
+
+static int admin_level_maxname = 0;
+g_admin_level_t *g_admin_levels[ MAX_ADMIN_LEVELS ];
+g_admin_admin_t *g_admin_admins[ MAX_ADMIN_ADMINS ];
+g_admin_ban_t *g_admin_bans[ MAX_ADMIN_BANS ];
+g_admin_command_t *g_admin_commands[ MAX_ADMIN_COMMANDS ];
+g_admin_namelog_t *g_admin_namelog[ MAX_ADMIN_NAMELOGS ];
+
+int G_admin_parse_time( const char *time );
+
+// match a certain flag within these flags
+// return state of whether flag was found or not,
+// set *perm to indicate whether found flag was + or -
+static qboolean admin_permission( char *flags, const char *flag, qboolean *perm )
+{
+ char *token, *token_p = flags;
+ qboolean all_found = qfalse;
+ qboolean base_perm = qfalse;
+
+ while( *( token = COM_Parse( &token_p ) ) )
+ {
+ *perm = qtrue;
+ if( *token == '-' || *token == '+' )
+ *perm = *token++ == '+';
+ if( !strcmp( token, flag ) )
+ return qtrue;
+ if( !strcmp( token, ADMF_ALLFLAGS ) )
+ {
+ all_found = qtrue;
+ base_perm = *perm;
+ }
+ }
+
+ if( all_found && flag[ 0 ] != '.' )
+ {
+ *perm = base_perm;
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+static int admin_adminlog_index = 0;
+g_admin_adminlog_t *g_admin_adminlog[ MAX_ADMIN_ADMINLOGS ];
+
+// This function should only be used directly when the client is connecting and thus has no GUID.
+// Else, use G_admin_permission()
+qboolean G_admin_permission_guid( char *guid, const char* flag )
+{
+ int i;
+ int l = 0;
+ qboolean perm = qfalse;
+
+ // Does the admin specifically have this flag granted/denied to them,
+ // irrespective of their admin level?
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) )
+ {
+ if( admin_permission( g_admin_admins[ i ]->flags, flag, &perm ) )
+ return perm;
+ l = g_admin_admins[ i ]->level;
+ break;
+ }
+ }
+
+ // If not, is this flag granted/denied for their admin level?
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_levels[ i ]->level == l )
+ return admin_permission( g_admin_levels[ i ]->flags, flag, &perm ) &&
+ perm;
+ }
+ return qfalse;
+}
+
+
+qboolean G_admin_permission( gentity_t *ent, const char *flag )
+{
+ if(!ent) return qtrue; //console always wins
+
+ return G_admin_permission_guid(ent->client->pers.guid, flag);
+}
+
+qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len )
+{
+ int i;
+ gclient_t *client;
+ char testName[ MAX_NAME_LENGTH ] = {""};
+ char name2[ MAX_NAME_LENGTH ] = {""};
+ int alphaCount = 0;
+
+ G_SanitiseString( name, name2, sizeof( name2) );
+
+ if( !Q_stricmp( name2, "UnnamedPlayer" ) )
+ return qtrue;
+
+ if( !Q_stricmp( name2, "console" ) )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' is invalid here", name2 ),
+ len );
+ return qfalse;
+ }
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ client = &level.clients[ i ];
+ if( client->pers.connected != CON_CONNECTING
+ && client->pers.connected != CON_CONNECTED )
+ {
+ continue;
+ }
+
+ // can rename ones self to the same name using different colors
+ if( i == ( ent - g_entities ) )
+ continue;
+
+ G_SanitiseString( client->pers.netname, testName, sizeof( testName) );
+ if( !Q_stricmp( name2, testName ) )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' is already in use", name ),
+ len );
+ return qfalse;
+ }
+ }
+
+ if( Q_isdigit( name2[ 0 ] ) || name2[ 0 ] == '-' )
+ {
+ Q_strncpyz( err, "Names cannot begin with a number or with a dash. Please choose another.", len );
+ return qfalse;
+ }
+
+ for( i = 0; name2[ i ] !='\0'; i++)
+ {
+ if( Q_isalpha( name2[ i ] ) )
+ alphaCount++;
+
+ if( name2[ i ] == ' ' )
+ {
+ if( name2[ i + 1 ] == '-' )
+ {
+ Q_strncpyz( err, "Names cannot contain a - preceded by a space. Please choose another.", len );
+ return qfalse;
+ }
+ }
+ }
+
+ if( alphaCount == 0 )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' does not include at least one letter. Please choose another.", name ), len );
+ return qfalse;
+ }
+
+ if( !g_adminNameProtect.string[ 0 ] )
+ return qtrue;
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( g_admin_admins[ i ]->level < 1 )
+ continue;
+ G_SanitiseString( g_admin_admins[ i ]->name, testName, sizeof( testName) );
+ if( !Q_stricmp( name2, testName ) &&
+ Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' belongs to an admin. "
+ "Please choose another.", name ), len );
+ return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+static qboolean admin_higher_guid( char *admin_guid, char *victim_guid )
+{
+ int i;
+ int alevel = 0;
+ int alevel2 = 0;
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( admin_guid, g_admin_admins[ i ]->guid ) )
+ {
+ alevel = g_admin_admins[ i ]->level;
+
+ // Novelty Levels should be equivelant to level 1
+ if( alevel > 9 )
+ alevel = 1;
+
+ break;
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( victim_guid, g_admin_admins[ i ]->guid ) )
+ {
+ alevel2 = g_admin_admins[ i ]->level;
+
+ // Novelty Levels should be equivelant to level 1
+ if( alevel2 > 9 )
+ alevel2 = 1;
+
+ if( alevel < alevel2 )
+ return qfalse;
+
+ if( strstr( g_admin_admins[ i ]->flags, va( "%s", ADMF_IMMUTABLE ) ) )
+ return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+static qboolean admin_higher( gentity_t *admin, gentity_t *victim )
+{
+
+ // console always wins
+ if( !admin )
+ return qtrue;
+ // just in case
+ if( !victim )
+ return qtrue;
+
+ return admin_higher_guid( admin->client->pers.guid,
+ victim->client->pers.guid );
+}
+
+static void admin_writeconfig_string( char *s, fileHandle_t f )
+{
+ char buf[ MAX_STRING_CHARS ];
+
+ buf[ 0 ] = '\0';
+ if( s[ 0 ] )
+ {
+ //Q_strcat(buf, sizeof(buf), s);
+ Q_strncpyz( buf, s, sizeof( buf ) );
+ trap_FS_Write( buf, strlen( buf ), f );
+ }
+ trap_FS_Write( "\n", 1, f );
+}
+
+static void admin_writeconfig_int( int v, fileHandle_t f )
+{
+ char buf[ 32 ];
+
+ Com_sprintf( buf, sizeof(buf), "%d", v );
+ if( buf[ 0 ] )
+ trap_FS_Write( buf, strlen( buf ), f );
+ trap_FS_Write( "\n", 1, f );
+}
+
+void admin_writeconfig( void )
+{
+ fileHandle_t f;
+ int len, i;
+ int t, expiretime;
+ char levels[ MAX_STRING_CHARS ] = {""};
+
+ if( !g_admin.string[ 0 ] )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: g_admin is not set. "
+ " configuration will not be saved to a file.\n" );
+ return;
+ }
+ t = trap_RealTime( NULL );
+ len = trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE );
+ if( len < 0 )
+ {
+ G_Printf( "admin_writeconfig: could not open g_admin file \"%s\"\n",
+ g_admin.string );
+ return;
+ }
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ trap_FS_Write( "[level]\n", 8, f );
+ trap_FS_Write( "level = ", 10, f );
+ admin_writeconfig_int( g_admin_levels[ i ]->level, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( g_admin_levels[ i ]->name, f );
+ trap_FS_Write( "flags = ", 10, f );
+ admin_writeconfig_string( g_admin_levels[ i ]->flags, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ // don't write level 0 users
+ if( g_admin_admins[ i ]->level < 1 )
+ continue;
+
+ //if set dont write admins that havent been seen in a while
+ expiretime = G_admin_parse_time( g_adminExpireTime.string );
+ if( expiretime > 0 ) {
+ //only expire level 1 people
+ if( t - expiretime > g_admin_admins[ i ]->seen && g_admin_admins[ i ]->level == 1 ) {
+ G_Printf("Admin %s has been expired.\n", g_admin_admins[ i ]->name );
+ continue;
+ }
+ }
+
+ trap_FS_Write( "[admin]\n", 8, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( g_admin_admins[ i ]->name, f );
+ trap_FS_Write( "guid = ", 10, f );
+ admin_writeconfig_string( g_admin_admins[ i ]->guid, f );
+ trap_FS_Write( "level = ", 10, f );
+ admin_writeconfig_int( g_admin_admins[ i ]->level, f );
+ trap_FS_Write( "flags = ", 10, f );
+ admin_writeconfig_string( g_admin_admins[ i ]->flags, f );
+ trap_FS_Write( "seen = ", 10, f );
+ admin_writeconfig_int( g_admin_admins[ i ]->seen, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ // don't write expired bans
+ // if expires is 0, then it's a perm ban
+ if( g_admin_bans[ i ]->expires != 0 &&
+ ( g_admin_bans[ i ]->expires - t ) < 1 )
+ continue;
+
+ trap_FS_Write( "[ban]\n", 6, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->name, f );
+ trap_FS_Write( "guid = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->guid, f );
+ trap_FS_Write( "ip = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->ip, f );
+ trap_FS_Write( "reason = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->reason, f );
+ trap_FS_Write( "made = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->made, f );
+ trap_FS_Write( "expires = ", 10, f );
+ admin_writeconfig_int( g_admin_bans[ i ]->expires, f );
+ if( g_admin_bans[ i ]->suspend > t ) {
+ trap_FS_Write( "suspend = ", 10, f );
+ admin_writeconfig_int( g_admin_bans[ i ]->suspend, f );
+ }
+ trap_FS_Write( "banner = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->banner, f );
+ trap_FS_Write( "blevel = ", 10, f );
+ admin_writeconfig_int( g_admin_bans[ i ]->bannerlevel, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ levels[ 0 ] = '\0';
+ trap_FS_Write( "[command]\n", 10, f );
+ trap_FS_Write( "command = ", 10, f );
+ admin_writeconfig_string( g_admin_commands[ i ]->command, f );
+ trap_FS_Write( "exec = ", 10, f );
+ admin_writeconfig_string( g_admin_commands[ i ]->exec, f );
+ trap_FS_Write( "desc = ", 10, f );
+ admin_writeconfig_string( g_admin_commands[ i ]->desc, f );
+ trap_FS_Write( "flag = ", 10, f );
+ admin_writeconfig_string( g_admin_commands[ i ]->flag, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ trap_FS_FCloseFile( f );
+}
+
+static void admin_readconfig_string( char **cnf, char *s, int size )
+{
+ char * t;
+
+ //COM_MatchToken(cnf, "=");
+ t = COM_ParseExt( cnf, qfalse );
+ if( !strcmp( t, "=" ) )
+ {
+ t = COM_ParseExt( cnf, qfalse );
+ }
+ else
+ {
+ G_Printf( "readconfig: warning missing = before "
+ "\"%s\" on line %d\n",
+ t,
+ COM_GetCurrentParseLine() );
+ }
+ s[ 0 ] = '\0';
+ while( t[ 0 ] )
+ {
+ if( ( s[ 0 ] == '\0' && strlen( t ) <= size )
+ || ( strlen( t ) + strlen( s ) < size ) )
+ {
+
+ Q_strcat( s, size, t );
+ Q_strcat( s, size, " " );
+ }
+ t = COM_ParseExt( cnf, qfalse );
+ }
+ // trim the trailing space
+ if( strlen( s ) > 0 && s[ strlen( s ) - 1 ] == ' ' )
+ s[ strlen( s ) - 1 ] = '\0';
+}
+
+static void admin_readconfig_int( char **cnf, int *v )
+{
+ char * t;
+
+ //COM_MatchToken(cnf, "=");
+ t = COM_ParseExt( cnf, qfalse );
+ if( !strcmp( t, "=" ) )
+ {
+ t = COM_ParseExt( cnf, qfalse );
+ }
+ else
+ {
+ G_Printf( "readconfig: warning missing = before "
+ "\"%s\" on line %d\n",
+ t,
+ COM_GetCurrentParseLine() );
+ }
+ *v = atoi( t );
+}
+
+// if we can't parse any levels from readconfig, set up default
+// ones to make new installs easier for admins
+static void admin_default_levels( void )
+{
+ g_admin_level_t * l;
+ int i;
+
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ G_Free( g_admin_levels[ i ] );
+ g_admin_levels[ i ] = NULL;
+ }
+ for( i = 0; i <= 5; i++ )
+ {
+ l = G_Alloc( sizeof( g_admin_level_t ) );
+ l->level = i;
+ *l->name = '\0';
+ *l->flags = '\0';
+ g_admin_levels[ i ] = l;
+ }
+
+ Q_strncpyz( g_admin_levels[ 0 ]->name, "^4Unknown Player",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 0 ]->flags,
+ "listplayers admintest help specme time",
+ sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 1 ]->name, "^5Server Regular",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 1 ]->flags,
+ "listplayers admintest help specme time",
+ sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 2 ]->name, "^6Team Manager",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 2 ]->flags,
+ "listplayers admintest help specme time putteam spec999 warn denybuild",
+ sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 3 ]->name, "^2Junior Admin",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 3 ]->flags,
+ "listplayers admintest help specme time putteam spec999 kick mute warn "
+ "denybuild ADMINCHAT SEESFULLLISTPLAYERS",
+ sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 4 ]->name, "^3Senior Admin",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 4 ]->flags,
+ "listplayers admintest help specme time putteam spec999 kick mute showbans "
+ "ban namelog warn denybuild decon ADMINCHAT SEESFULLLISTPLAYERS",
+ sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 5 ]->name, "^1Server Operator",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 5 ]->flags,
+ "ALLFLAGS -INCOGNITO -IMMUTABLE -DBUILDER -BANIMMUNITY",
+ sizeof( l->flags ) );
+}
+
+// return a level for a player entity.
+int G_admin_level( gentity_t *ent )
+{
+ int i;
+ qboolean found = qfalse;
+
+ if( !ent )
+ {
+ return MAX_ADMIN_LEVELS;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+
+ found = qtrue;
+ break;
+ }
+ }
+
+ if( found )
+ {
+ return g_admin_admins[ i ]->level;
+ }
+
+ return 0;
+}
+
+// set a player's adminname
+void G_admin_set_adminname( gentity_t *ent )
+{
+ int i;
+ qboolean found = qfalse;
+
+ if( !ent )
+ {
+ return;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+
+ if( found )
+ {
+ Q_strncpyz( ent->client->pers.adminName, g_admin_admins[ i ]->name, sizeof( ent->client->pers.adminName ) );
+ }
+ else
+ {
+ Q_strncpyz( ent->client->pers.adminName, "", sizeof( ent->client->pers.adminName ) );
+ }
+}
+
+// Get an admin's registered name
+const char *G_admin_get_adminname( gentity_t *ent )
+{
+ int i;
+
+ if( !ent )
+ return "console";
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ return g_admin_admins[ i ]->name;
+ }
+
+ return ent->client->pers.netname;
+}
+
+// Get an admin's name to print as the user of various commands,
+// obeying admin stealth settings when necessary
+char* G_admin_adminPrintName( gentity_t *ent )
+{
+ char *out;
+
+ if( !ent->client->pers.adminLevel )
+ {
+ out = "";
+ return out;
+ }
+
+ if( G_admin_permission( ent, ADMF_ADMINSTEALTH ) )
+ {
+ out = ent->client->pers.adminName;
+ }
+ else
+ {
+ out = ent->client->pers.netname;
+ }
+
+
+ return out;
+}
+
+static void admin_log( gentity_t *admin, char *cmd, int skiparg )
+{
+ fileHandle_t f;
+ int len, i, j;
+ char string[ MAX_STRING_CHARS ], decoloured[ MAX_STRING_CHARS ];
+ int min, tens, sec;
+ g_admin_admin_t *a;
+ g_admin_level_t *l;
+ char flags[ MAX_ADMIN_FLAGS * 2 ];
+ gentity_t *victim = NULL;
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ];
+
+ if( !g_adminLog.string[ 0 ] )
+ return ;
+
+
+ len = trap_FS_FOpenFile( g_adminLog.string, &f, FS_APPEND );
+ if( len < 0 )
+ {
+ G_Printf( "admin_log: error could not open %s\n", g_adminLog.string );
+ return ;
+ }
+
+ sec = (level.time - level.startTime) / 1000;
+ min = sec / 60;
+ sec -= min * 60;
+ tens = sec / 10;
+ sec -= tens * 10;
+
+ *flags = '\0';
+ if( admin )
+ {
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid , admin->client->pers.guid ) )
+ {
+
+ a = g_admin_admins[ i ];
+ Q_strncpyz( flags, a->flags, sizeof( flags ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == a->level )
+ {
+ l = g_admin_levels[ j ];
+ Q_strcat( flags, sizeof( flags ), l->flags );
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if( G_SayArgc() > 1 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) == 1 )
+ {
+ victim = &g_entities[ pids[ 0 ] ];
+ }
+ }
+
+ if( victim && Q_stricmp( cmd, "attempted" ) )
+ {
+ Com_sprintf( string, sizeof( string ),
+ "%3i:%i%i: %i: %s: %s (%s): %s: %s: %s: %s: \"%s\"\n",
+ min,
+ tens,
+ sec,
+ ( admin ) ? admin->s.clientNum : -1,
+ ( admin ) ? admin->client->pers.guid
+ : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ ( admin ) ? admin->client->pers.netname : "console",
+ ( admin ) ? admin->client->pers.adminName : "console",
+ flags,
+ cmd,
+ victim->client->pers.guid,
+ victim->client->pers.netname,
+ G_SayConcatArgs( 2 + skiparg ) );
+ }
+ else
+ {
+ Com_sprintf( string, sizeof( string ),
+ "%3i:%i%i: %i: %s: %s (%s): %s: %s: \"%s\"\n",
+ min,
+ tens,
+ sec,
+ ( admin ) ? admin->s.clientNum : -1,
+ ( admin ) ? admin->client->pers.guid
+ : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ ( admin ) ? admin->client->pers.netname : "console",
+ ( admin ) ? admin->client->pers.adminName : "console",
+ flags,
+ cmd,
+ G_SayConcatArgs( 1 + skiparg ) );
+ }
+
+ if( g_decolourLogfiles.integer )
+ {
+ G_DecolorString( string, decoloured );
+ trap_FS_Write( decoloured, strlen( decoloured ), f );
+ }
+ else
+ {
+ trap_FS_Write( string, strlen( string ), f );
+ }
+ trap_FS_FCloseFile( f );
+
+ if ( !Q_stricmp( cmd, "attempted" ) )
+ {
+ Com_sprintf( string, sizeof( string ),
+ "%s^7 (%i) %s: %s",
+ ( admin ) ? admin->client->pers.netname : "console",
+ ( admin ) ? admin->s.clientNum : -1,
+ cmd,
+ G_SayConcatArgs( 1 + skiparg ) );
+ G_AdminsPrintf("%s\n",string);
+ }
+
+ G_LogPrintf("Admin Command: %s^7 (%s): %s %s\n",( admin ) ? admin->client->pers.netname : "console", ( admin ) ? admin->client->pers.adminName : "console", cmd, G_SayConcatArgs( 1 + skiparg ));
+}
+
+static int admin_listadmins( gentity_t *ent, int start, char *search, int minlevel )
+{
+ int drawn = 0;
+ char guid_stub[9];
+ char name[ MAX_NAME_LENGTH ] = {""};
+ char name2[ MAX_NAME_LENGTH ] = {""};
+ char lname[ MAX_NAME_LENGTH ] = {""};
+ char lname_fmt[ 5 ];
+ int i,j;
+ gentity_t *vic;
+ int l = 0;
+ qboolean dup = qfalse;
+
+ ADMBP_begin();
+
+ // print out all connected players regardless of level if name searching
+ for( i = 0; i < level.maxclients && search[ 0 ]; i++ )
+ {
+ vic = &g_entities[ i ];
+
+ if( vic->client && vic->client->pers.connected != CON_CONNECTED )
+ continue;
+
+ l = vic->client->pers.adminLevel;
+
+ G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) );
+ if( !strstr( name, search ) )
+ continue;
+
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = vic->client->pers.guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+
+ lname[ 0 ] = '\0';
+ Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == l )
+ {
+ G_DecolorString( g_admin_levels[ j ]->name, lname );
+ Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
+ ( admin_level_maxname + strlen( g_admin_levels[ j ]->name )
+ - strlen( lname ) ) );
+ Com_sprintf( lname, sizeof( lname ), lname_fmt,
+ g_admin_levels[ j ]->name );
+ break;
+ }
+ }
+ ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n",
+ i,
+ l,
+ lname,
+ guid_stub,
+ vic->client->pers.netname ) );
+ drawn++;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]
+ && drawn < MAX_ADMIN_LISTITEMS; i++ )
+ if( g_admin_admins[ i ]->level >= minlevel )
+ {
+
+ if( start )
+ {
+ start--;
+ continue;
+ }
+
+ if( search[ 0 ] )
+ {
+ G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) );
+ if( !strstr( name, search ) )
+ continue;
+
+ // verify we don't have the same guid/name pair in connected players
+ // since we don't want to draw the same player twice
+ dup = qfalse;
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ vic = &g_entities[ j ];
+ if( !vic->client || vic->client->pers.connected != CON_CONNECTED )
+ continue;
+ G_SanitiseString( vic->client->pers.netname, name2, sizeof( name2) );
+ if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid )
+ && strstr( name2, search ) )
+ {
+ dup = qtrue;
+ break;
+ }
+ }
+ if( dup )
+ continue;
+ }
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = g_admin_admins[ i ]->guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+
+ lname[ 0 ] = '\0';
+ Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == g_admin_admins[ i ]->level )
+ {
+ G_DecolorString( g_admin_levels[ j ]->name, lname );
+ Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
+ ( admin_level_maxname + strlen( g_admin_levels[ j ]->name )
+ - strlen( lname ) ) );
+ Com_sprintf( lname, sizeof( lname ), lname_fmt,
+ g_admin_levels[ j ]->name );
+ break;
+ }
+ }
+ ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n",
+ ( i + MAX_CLIENTS ),
+ g_admin_admins[ i ]->level,
+ lname,
+ guid_stub,
+ g_admin_admins[ i ]->name ) );
+ drawn++;
+ }
+ ADMBP_end();
+ return drawn;
+}
+
+void G_admin_duration( int secs, char *duration, int dursize )
+{
+
+ if( secs > ( 60 * 60 * 24 * 365 * 50 ) || secs < 0 )
+ Q_strncpyz( duration, "PERMANENT", dursize );
+ else if( secs >= ( 60 * 60 * 24 * 365 ) )
+ Com_sprintf( duration, dursize, "%1.1f years",
+ ( secs / ( 60 * 60 * 24 * 365.0f ) ) );
+ else if( secs >= ( 60 * 60 * 24 * 90 ) )
+ Com_sprintf( duration, dursize, "%1.1f weeks",
+ ( secs / ( 60 * 60 * 24 * 7.0f ) ) );
+ else if( secs >= ( 60 * 60 * 24 ) )
+ Com_sprintf( duration, dursize, "%1.1f days",
+ ( secs / ( 60 * 60 * 24.0f ) ) );
+ else if( secs >= ( 60 * 60 ) )
+ Com_sprintf( duration, dursize, "%1.1f hours",
+ ( secs / ( 60 * 60.0f ) ) );
+ else if( secs >= 60 )
+ Com_sprintf( duration, dursize, "%1.1f minutes",
+ ( secs / 60.0f ) );
+ else
+ Com_sprintf( duration, dursize, "%i seconds", secs );
+}
+
+qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen )
+{
+ static char lastConnectIP[ 16 ] = {""};
+ static int lastConnectTime = 0;
+ char guid[ 33 ];
+ char ip[ 16 ];
+ char *value;
+ int i;
+ unsigned int userIP = 0, intIP = 0, tempIP;
+ int IP[5], k, mask, ipscanfcount;
+ int t;
+ char notice[51];
+ qboolean ignoreIP = qfalse;
+
+ trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) );
+
+ *reason = '\0';
+
+ if( !*userinfo )
+ return qfalse;
+
+ value = Info_ValueForKey( userinfo, "ip" );
+ Q_strncpyz( ip, value, sizeof( ip ) );
+ // strip port
+ value = strchr( ip, ':' );
+ if ( value )
+ *value = '\0';
+
+ if( !*ip )
+ return qfalse;
+
+ value = Info_ValueForKey( userinfo, "cl_guid" );
+ Q_strncpyz( guid, value, sizeof( guid ) );
+
+ t = trap_RealTime( NULL );
+ memset( IP, 0, sizeof( IP ));
+ sscanf(ip, "%i.%i.%i.%i", &IP[4], &IP[3], &IP[2], &IP[1]);
+ for(k = 4; k >= 1; k--)
+ {
+ if(!IP[k]) continue;
+ userIP |= IP[k] << 8*(k-1);
+ }
+ ignoreIP = G_admin_permission_guid( guid , ADMF_BAN_IMMUNITY );
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ // 0 is for perm ban
+ if( g_admin_bans[ i ]->expires != 0 &&
+ ( g_admin_bans[ i ]->expires - t ) < 1 )
+ continue;
+ //if suspend time is over continue
+ if( g_admin_bans[ i ]->suspend >= t )
+ continue;
+
+ if( !ignoreIP )
+ {
+ tempIP = userIP;
+ intIP = 0;
+ mask = -1;
+
+ memset( IP, 0, sizeof( IP ));
+ ipscanfcount = sscanf(g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]);
+
+ if( ipscanfcount == 4 )
+ mask = -1;
+ else if( ipscanfcount == 5 )
+ mask = IP[0];
+ else if( ipscanfcount > 0 && ipscanfcount < 4 )
+ mask = 8 * ipscanfcount;
+ else
+ continue;
+
+ for(k = 4; k >= 1; k--)
+ {
+ if(!IP[k]) continue;
+ intIP |= IP[k] << 8*(k-1);
+ }
+
+ if(mask > 0 && mask <= 32)
+ {
+ tempIP &= ~((1 << (32-mask)) - 1);
+ intIP &= ~((1 << (32-mask)) - 1);
+ }
+
+ if( intIP == tempIP || mask == 0 )
+ {
+ char duration[ 32 ];
+ G_admin_duration( ( g_admin_bans[ i ]->expires - t ),
+ duration, sizeof( duration ) );
+
+ // flood protected
+ if( t - lastConnectTime >= 300 ||
+ Q_stricmp( lastConnectIP, ip ) )
+ {
+ lastConnectTime = t;
+ Q_strncpyz( lastConnectIP, ip, sizeof( lastConnectIP ) );
+
+ G_WarningsPrintf(
+ "ban",
+ "Banned player %s^7 (%s^7) tried to connect (ban #%i on %s by %s^7 expires %s reason: %s^7 )\n",
+ Info_ValueForKey( userinfo, "name" ),
+ g_admin_bans[ i ]->name,
+ i+1,
+ ip,
+ g_admin_bans[ i ]->banner,
+ duration,
+ g_admin_bans[ i ]->reason );
+ }
+
+ Com_sprintf(
+ reason,
+ rlen,
+ "You have been banned by %s^7 reason: %s^7 expires: %s %s",
+ g_admin_bans[ i ]->banner,
+ g_admin_bans[ i ]->reason,
+ duration,
+ notice
+ );
+ G_LogPrintf("Banned player tried to connect from IP %s\n", ip);
+ return qtrue;
+ }
+ }
+ if( *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) )
+ {
+ char duration[ 32 ];
+ G_admin_duration( ( g_admin_bans[ i ]->expires - t ),
+ duration, sizeof( duration ) );
+ Com_sprintf(
+ reason,
+ rlen,
+ "You have been banned by %s^7 reason: %s^7 expires: %s",
+ g_admin_bans[ i ]->banner,
+ g_admin_bans[ i ]->reason,
+ duration
+ );
+ G_Printf("Banned player tried to connect with GUID %s\n", guid);
+ return qtrue;
+ }
+ }
+ if ( *guid )
+ {
+ int count = 0;
+ qboolean valid = qtrue;
+
+ while( guid[ count ] != '\0' && valid )
+ {
+ if( (guid[ count ] < '0' || guid[ count ] > '9') &&
+ (guid[ count ] < 'A' || guid[ count ] > 'F') )
+ {
+ valid = qfalse;
+ }
+ count++;
+ }
+ if( !valid || count != 32 )
+ {
+ Com_sprintf( reason, rlen, "Invalid client data" );
+ G_Printf("Player with invalid GUID [%s] connect from IP %s\n", guid, ip);
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+qboolean G_admin_cmd_check( gentity_t *ent, qboolean say )
+{
+ int i;
+ char command[ MAX_ADMIN_CMD_LEN ];
+ char *cmd;
+ int skip = 0;
+
+ command[ 0 ] = '\0';
+ G_SayArgv( 0, command, sizeof( command ) );
+ if( !Q_stricmp( command, "say" ) ||
+ ( G_admin_permission( ent, ADMF_TEAMCHAT_CMD ) &&
+ ( !Q_stricmp( command, "say_team" ) ) ) )
+ {
+ skip = 1;
+ G_SayArgv( 1, command, sizeof( command ) );
+ }
+ if( !command[ 0 ] )
+ return qfalse;
+
+ if( command[ 0 ] == '!' )
+ {
+ cmd = &command[ 1 ];
+ }
+ else
+ {
+ return qfalse;
+ }
+
+ // Flood limit. If they're talking too fast, determine that and return.
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
+ return qtrue;
+ }
+
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ if( Q_stricmp( cmd, g_admin_commands[ i ]->command ) )
+ continue;
+
+ if( G_admin_permission( ent, g_admin_commands[ i ]->flag ) )
+ {
+ trap_SendConsoleCommand( EXEC_APPEND, g_admin_commands[ i ]->exec );
+ admin_log( ent, cmd, skip );
+ G_admin_adminlog_log( ent, cmd, NULL, skip, qtrue );
+ }
+ else
+ {
+ ADMP( va( "^3!%s: ^7permission denied\n", g_admin_commands[ i ]->command ) );
+ admin_log( ent, "attempted", skip - 1 );
+ G_admin_adminlog_log( ent, cmd, NULL, skip, qfalse );
+ }
+ return qtrue;
+ }
+
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) )
+ continue;
+
+ if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) )
+ {
+ g_admin_cmds[ i ].handler( ent, skip );
+ admin_log( ent, cmd, skip );
+ G_admin_adminlog_log( ent, cmd, NULL, skip, qtrue );
+ }
+ else
+ {
+ ADMP( va( "^3!%s: ^7permission denied\n", g_admin_cmds[ i ].keyword ) );
+ admin_log( ent, "attempted", skip - 1 );
+ G_admin_adminlog_log( ent, cmd, NULL, skip, qfalse );
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+void G_admin_namelog_cleanup( )
+{
+ int i;
+
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ G_Free( g_admin_namelog[ i ] );
+ g_admin_namelog[ i ] = NULL;
+ }
+}
+
+void G_admin_namelog_update( gclient_t *client, qboolean disconnect )
+{
+ int i, j;
+ g_admin_namelog_t *namelog;
+ char n1[ MAX_NAME_LENGTH ];
+ char n2[ MAX_NAME_LENGTH ];
+ int clientNum = ( client - level.clients );
+
+ if ( client->sess.invisible == qfalse )
+ {
+ G_admin_seen_update( client->pers.guid );
+ }
+
+ G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) );
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ if( disconnect && g_admin_namelog[ i ]->slot != clientNum )
+ continue;
+
+ if( !disconnect && !( g_admin_namelog[ i ]->slot == clientNum ||
+ g_admin_namelog[ i ]->slot == -1 ) )
+ {
+ continue;
+ }
+
+ if( !Q_stricmp( client->pers.ip, g_admin_namelog[ i ]->ip )
+ && !Q_stricmp( client->pers.guid, g_admin_namelog[ i ]->guid ) )
+ {
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
+ && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( !Q_stricmp( n1, n2 ) )
+ break;
+ }
+ if( j == MAX_ADMIN_NAMELOG_NAMES )
+ j = MAX_ADMIN_NAMELOG_NAMES - 1;
+ Q_strncpyz( g_admin_namelog[ i ]->name[ j ], client->pers.netname,
+ sizeof( g_admin_namelog[ i ]->name[ j ] ) );
+ g_admin_namelog[ i ]->slot = ( disconnect ) ? -1 : clientNum;
+
+ // if this player is connecting, they are no longer banned
+ if( !disconnect )
+ g_admin_namelog[ i ]->banned = qfalse;
+
+ //check other things like if user was denybuild or muted or denyweapon and restore them
+ if( !disconnect )
+ {
+ if( g_admin_namelog[ i ]->muted )
+ {
+ client->pers.muted = qtrue;
+ client->pers.muteExpires = g_admin_namelog[ i ]->muteExpires;
+ G_AdminsPrintf( "^7%s^7's mute has been restored\n", client->pers.netname );
+ g_admin_namelog[ i ]->muted = qfalse;
+ }
+ if( g_admin_namelog[ i ]->denyBuild )
+ {
+ client->pers.denyBuild = qtrue;
+ G_AdminsPrintf( "^7%s^7's Denybuild has been restored\n", client->pers.netname );
+ g_admin_namelog[ i ]->denyBuild = qfalse;
+ }
+ if( g_admin_namelog[ i ]->denyHumanWeapons > 0 || g_admin_namelog[ i ]->denyAlienClasses > 0 )
+ {
+ if( g_admin_namelog[ i ]->denyHumanWeapons > 0 )
+ client->pers.denyHumanWeapons = g_admin_namelog[ i ]->denyHumanWeapons;
+ if( g_admin_namelog[ i ]->denyAlienClasses > 0 )
+ client->pers.denyAlienClasses = g_admin_namelog[ i ]->denyAlienClasses;
+
+ G_AdminsPrintf( "^7%s^7's Denyweapon has been restored\n", client->pers.netname );
+ g_admin_namelog[ i ]->denyHumanWeapons = 0;
+ g_admin_namelog[ i ]->denyAlienClasses = 0;
+ }
+ if( g_admin_namelog[ i ]->specExpires > 0 )
+ {
+ client->pers.specExpires = g_admin_namelog[ i ]->specExpires;
+ G_AdminsPrintf( "^7%s^7's Putteam spectator has been restored\n", client->pers.netname );
+ g_admin_namelog[ i ]->specExpires = 0;
+ }
+ if( g_admin_namelog[ i ]->voteCount > 0 )
+ {
+ client->pers.voteCount = g_admin_namelog[ i ]->voteCount;
+ g_admin_namelog[ i ]->voteCount = 0;
+ }
+ }
+ else
+ {
+ //for mute
+ if( G_IsMuted( client ) )
+ {
+ g_admin_namelog[ i ]->muted = qtrue;
+ g_admin_namelog[ i ]->muteExpires = client->pers.muteExpires;
+ }
+ //denybuild
+ if( client->pers.denyBuild )
+ {
+ g_admin_namelog[ i ]->denyBuild = qtrue;
+ }
+ //denyweapon humans
+ if( client->pers.denyHumanWeapons > 0 )
+ {
+ g_admin_namelog[ i ]->denyHumanWeapons = client->pers.denyHumanWeapons;
+ }
+ //denyweapon aliens
+ if( client->pers.denyAlienClasses > 0 )
+ {
+ g_admin_namelog[ i ]->denyAlienClasses = client->pers.denyAlienClasses;
+ }
+ //putteam spec
+ if( client->pers.specExpires > 0 )
+ {
+ g_admin_namelog[ i ]->specExpires = client->pers.specExpires;
+ }
+ if( client->pers.voteCount > 0 )
+ {
+ g_admin_namelog[ i ]->voteCount = client->pers.voteCount;
+ }
+ }
+
+ return;
+ }
+ }
+ if( i >= MAX_ADMIN_NAMELOGS )
+ {
+ G_Printf( "G_admin_namelog_update: warning, g_admin_namelogs overflow\n" );
+ return;
+ }
+ namelog = G_Alloc( sizeof( g_admin_namelog_t ) );
+ memset( namelog, 0, sizeof( *namelog ) );
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES ; j++ )
+ namelog->name[ j ][ 0 ] = '\0';
+ Q_strncpyz( namelog->ip, client->pers.ip, sizeof( namelog->ip ) );
+ Q_strncpyz( namelog->guid, client->pers.guid, sizeof( namelog->guid ) );
+ Q_strncpyz( namelog->name[ 0 ], client->pers.netname,
+ sizeof( namelog->name[ 0 ] ) );
+ namelog->slot = ( disconnect ) ? -1 : clientNum;
+ g_admin_namelog[ i ] = namelog;
+}
+
+qboolean G_admin_readconfig( gentity_t *ent, int skiparg )
+{
+ g_admin_level_t * l = NULL;
+ g_admin_admin_t *a = NULL;
+ g_admin_ban_t *b = NULL;
+ g_admin_command_t *c = NULL;
+ int lc = 0, ac = 0, bc = 0, cc = 0;
+ fileHandle_t f;
+ int len;
+ char *cnf, *cnf2;
+ char *t;
+ qboolean level_open, admin_open, ban_open, command_open;
+ int i;
+
+ G_admin_cleanup();
+
+ if( !g_admin.string[ 0 ] )
+ {
+ ADMP( "^3!readconfig: g_admin is not set, not loading configuration "
+ "from a file\n" );
+ admin_default_levels();
+ return qfalse;
+ }
+
+ len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ) ;
+ if( len < 0 )
+ {
+ ADMP( va( "^3!readconfig: ^7could not open admin config file %s\n",
+ g_admin.string ) );
+ admin_default_levels();
+ return qfalse;
+ }
+ cnf = G_Alloc( len + 1 );
+ cnf2 = cnf;
+ trap_FS_Read( cnf, len, f );
+ *( cnf + len ) = '\0';
+ trap_FS_FCloseFile( f );
+
+ t = COM_Parse( &cnf );
+ level_open = admin_open = ban_open = command_open = qfalse;
+ while( *t )
+ {
+ if( !Q_stricmp( t, "[level]" ) ||
+ !Q_stricmp( t, "[admin]" ) ||
+ !Q_stricmp( t, "[ban]" ) ||
+ !Q_stricmp( t, "[command]" ) )
+ {
+
+ if( level_open )
+ g_admin_levels[ lc++ ] = l;
+ else if( admin_open )
+ g_admin_admins[ ac++ ] = a;
+ else if( ban_open )
+ g_admin_bans[ bc++ ] = b;
+ else if( command_open )
+ g_admin_commands[ cc++ ] = c;
+ level_open = admin_open =
+ ban_open = command_open = qfalse;
+ }
+
+ if( level_open )
+ {
+ if( !Q_stricmp( t, "level" ) )
+ {
+ admin_readconfig_int( &cnf, &l->level );
+ }
+ else if( !Q_stricmp( t, "name" ) )
+ {
+ admin_readconfig_string( &cnf, l->name, sizeof( l->name ) );
+ }
+ else if( !Q_stricmp( t, "flags" ) )
+ {
+ admin_readconfig_string( &cnf, l->flags, sizeof( l->flags ) );
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[level] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+ }
+ else if( admin_open )
+ {
+ if( !Q_stricmp( t, "name" ) )
+ {
+ admin_readconfig_string( &cnf, a->name, sizeof( a->name ) );
+ }
+ else if( !Q_stricmp( t, "guid" ) )
+ {
+ admin_readconfig_string( &cnf, a->guid, sizeof( a->guid ) );
+ }
+ else if( !Q_stricmp( t, "level" ) )
+ {
+ admin_readconfig_int( &cnf, &a->level );
+ }
+ else if( !Q_stricmp( t, "flags" ) )
+ {
+ admin_readconfig_string( &cnf, a->flags, sizeof( a->flags ) );
+ }
+ else if( !Q_stricmp( t, "seen" ) )
+ {
+ admin_readconfig_int( &cnf, &a->seen );
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[admin] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+
+ }
+ else if( ban_open )
+ {
+ if( !Q_stricmp( t, "name" ) )
+ {
+ admin_readconfig_string( &cnf, b->name, sizeof( b->name ) );
+ }
+ else if( !Q_stricmp( t, "guid" ) )
+ {
+ admin_readconfig_string( &cnf, b->guid, sizeof( b->guid ) );
+ }
+ else if( !Q_stricmp( t, "ip" ) )
+ {
+ admin_readconfig_string( &cnf, b->ip, sizeof( b->ip ) );
+ }
+ else if( !Q_stricmp( t, "reason" ) )
+ {
+ admin_readconfig_string( &cnf, b->reason, sizeof( b->reason ) );
+ }
+ else if( !Q_stricmp( t, "made" ) )
+ {
+ admin_readconfig_string( &cnf, b->made, sizeof( b->made ) );
+ }
+ else if( !Q_stricmp( t, "expires" ) )
+ {
+ admin_readconfig_int( &cnf, &b->expires );
+ }
+ else if( !Q_stricmp( t, "suspend" ) )
+ {
+ admin_readconfig_int( &cnf, &b->suspend );
+ }
+ else if( !Q_stricmp( t, "banner" ) )
+ {
+ admin_readconfig_string( &cnf, b->banner, sizeof( b->banner ) );
+ }
+ else if( !Q_stricmp( t, "blevel" ) )
+ {
+ admin_readconfig_int( &cnf, &b->bannerlevel );
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[ban] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+ }
+ else if( command_open )
+ {
+ if( !Q_stricmp( t, "command" ) )
+ {
+ admin_readconfig_string( &cnf, c->command, sizeof( c->command ) );
+ }
+ else if( !Q_stricmp( t, "exec" ) )
+ {
+ admin_readconfig_string( &cnf, c->exec, sizeof( c->exec ) );
+ }
+ else if( !Q_stricmp( t, "desc" ) )
+ {
+ admin_readconfig_string( &cnf, c->desc, sizeof( c->desc ) );
+ }
+ else if( !Q_stricmp( t, "flag" ) )
+ {
+ admin_readconfig_string( &cnf, c->flag, sizeof( c->flag ) );
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[command] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+ }
+
+ if( !Q_stricmp( t, "[level]" ) )
+ {
+ if( lc >= MAX_ADMIN_LEVELS )
+ return qfalse;
+ l = G_Alloc( sizeof( g_admin_level_t ) );
+ l->level = 0;
+ *l->name = '\0';
+ *l->flags = '\0';
+ level_open = qtrue;
+ }
+ else if( !Q_stricmp( t, "[admin]" ) )
+ {
+ if( ac >= MAX_ADMIN_ADMINS )
+ return qfalse;
+ a = G_Alloc( sizeof( g_admin_admin_t ) );
+ *a->name = '\0';
+ *a->guid = '\0';
+ a->level = 0;
+ *a->flags = '\0';
+ a->seen = 0;
+ admin_open = qtrue;
+ }
+ else if( !Q_stricmp( t, "[ban]" ) )
+ {
+ if( bc >= MAX_ADMIN_BANS )
+ return qfalse;
+ b = G_Alloc( sizeof( g_admin_ban_t ) );
+ *b->name = '\0';
+ *b->guid = '\0';
+ *b->ip = '\0';
+ *b->made = '\0';
+ b->expires = 0;
+ b->suspend = 0;
+ *b->reason = '\0';
+ b->bannerlevel = 0;
+ ban_open = qtrue;
+ }
+ else if( !Q_stricmp( t, "[command]" ) )
+ {
+ if( cc >= MAX_ADMIN_COMMANDS )
+ return qfalse;
+ c = G_Alloc( sizeof( g_admin_command_t ) );
+ *c->command = '\0';
+ *c->exec = '\0';
+ *c->desc = '\0';
+ *c->flag = '\0';
+ command_open = qtrue;
+ }
+ t = COM_Parse( &cnf );
+ }
+ if( level_open )
+ {
+
+ g_admin_levels[ lc++ ] = l;
+ }
+ if( admin_open )
+ g_admin_admins[ ac++ ] = a;
+ if( ban_open )
+ g_admin_bans[ bc++ ] = b;
+ if( command_open )
+ g_admin_commands[ cc++ ] = c;
+ G_Free( cnf2 );
+ ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n",
+ lc, ac, bc, cc ) );
+ if( lc == 0 )
+ admin_default_levels();
+ else
+ {
+ char n[ MAX_NAME_LENGTH ] = {""};
+
+ // max printable name length for formatting
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ G_DecolorString( l->name, n );
+ if( strlen( n ) > admin_level_maxname )
+ admin_level_maxname = strlen( n );
+ }
+ }
+ // reset adminLevel
+ for( i = 0; i < level.maxclients; i++ )
+ if( level.clients[ i ].pers.connected != CON_DISCONNECTED )
+ level.clients[ i ].pers.adminLevel = G_admin_level( &g_entities[ i ] );
+ return qtrue;
+}
+
+qboolean G_admin_time( gentity_t *ent, int skiparg )
+{
+ qtime_t qt;
+ int t;
+
+ t = trap_RealTime( &qt );
+ ADMP( va( "^3!time: ^7local time is %02i:%02i:%02i\n",
+ qt.tm_hour, qt.tm_min, qt.tm_sec ) );
+
+ return qtrue;
+}
+
+static int G_admin_find_slot( gentity_t *ent, char *namearg, const char *command )
+{
+ char name[ MAX_NAME_LENGTH ];
+ char testname[ MAX_NAME_LENGTH ];
+ char *guid = NULL;
+ int matches = 0;
+ int id = -1;
+ int i;
+ qboolean numeric = qtrue;
+
+ G_SanitiseString( namearg, name, sizeof( name ) );
+ for( i = 0; i < sizeof( name ) && name[ i ] ; i++ )
+ {
+ if( !isdigit( name[ i ] ) )
+ {
+ numeric = qfalse;
+ break;
+ }
+ }
+ if( numeric )
+ {
+ id = atoi( name );
+
+ if( id >= 0 && id < level.maxclients )
+ {
+ gentity_t *vic;
+
+ vic = &g_entities[ id ];
+ if( vic && vic->client && vic->client->pers.connected != CON_DISCONNECTED )
+ return id;
+
+ ADMP( va( "^3!%s:^7 no one connected by that slot number\n", command ) );
+ return -1;
+ }
+ if( id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS &&
+ g_admin_admins[ id - MAX_CLIENTS ] )
+ {
+ return id;
+ }
+
+ ADMP( va( "^3!%s:^7 no match for slot or admin number %d\n", command, id ) );
+ return -1;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && matches < 2; i++ )
+ {
+ G_SanitiseString( g_admin_admins[ i ]->name, testname, sizeof( testname ) );
+ if( strstr( testname, name ) )
+ {
+ id = i + MAX_CLIENTS;
+ guid = g_admin_admins[ i ]->guid;
+ matches++;
+ }
+ }
+ for( i = 0; i < level.maxclients && matches < 2; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( matches && guid && !Q_stricmp( level.clients[ i ].pers.guid, guid ) )
+ continue;
+
+ G_SanitiseString( level.clients[ i ].pers.netname, testname, sizeof( testname ) );
+ if( strstr( testname, name ) )
+ {
+ id = i;
+ matches++;
+ }
+ }
+
+ if( matches == 0 )
+ {
+ ADMP( va( "^3!%s:^7 no match, use !listplayers or !listadmins to "
+ "find an appropriate number to use instead of name.\n", command ) );
+ return -1;
+ }
+
+ if( matches == 1 )
+ return id;
+
+ ADMP( va( "^3!%s:^7 multiple matches, use the admin number instead:\n", command ) );
+ admin_listadmins( ent, 0, name, 0 );
+
+ return -1;
+}
+
+static int G_admin_find_admin_slot( gentity_t *ent, char *namearg, char *command, char *nick, int nick_len )
+{
+ gentity_t *vic;
+ char *guid;
+ int id;
+ int i;
+
+ if ( nick )
+ nick[ 0 ] = '\0';
+
+ id = G_admin_find_slot( ent, namearg, command );
+ if( id < 0 )
+ return -1;
+
+ if( id < MAX_CLIENTS )
+ {
+ vic = &g_entities[ id ];
+ guid = vic->client->pers.guid;
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) )
+ {
+ id = i + MAX_CLIENTS;
+ if( nick )
+ Q_strncpyz( nick, vic->client->pers.netname, nick_len );
+ break;
+ }
+ }
+ if( id < MAX_CLIENTS )
+ {
+ ADMP( va( "^3!%s:^7 player is not !registered\n", command ) );
+ return -1;
+ }
+ }
+
+ id -= MAX_CLIENTS;
+ if( nick && !nick[ 0 ] )
+ Q_strncpyz( nick, g_admin_admins[ id ]->name, nick_len );
+
+ return id;
+}
+
+qboolean G_admin_setlevel( gentity_t *ent, int skiparg )
+{
+ char lstr[ 11 ]; // 10 is max strlen() for 32-bit int
+ char adminname[ MAX_NAME_LENGTH ] = {""};
+ char testname[ MAX_NAME_LENGTH ] = {""};
+ char guid[ 33 ];
+ int l, i;
+ gentity_t *vic = NULL;
+ qboolean updated = qfalse;
+ g_admin_admin_t *a;
+ qboolean found = qfalse;
+ int id = -1;
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!setlevel: ^7usage: !setlevel [name|slot#] [level]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, testname, sizeof( testname ) );
+ G_SayArgv( 2 + skiparg, lstr, sizeof( lstr ) );
+ l = atoi( lstr );
+
+ if( ent && l > ent->client->pers.adminLevel )
+ {
+ ADMP( "^3!setlevel: ^7you may not use !setlevel to set a level higher "
+ "than your current level\n" );
+ return qfalse;
+ }
+
+ // if admin is activated for the first time on a running server, we need
+ // to ensure at least the default levels get created
+ if( !ent && !g_admin_levels[ 0 ] )
+ G_admin_readconfig(NULL, 0);
+
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_levels[ i ]->level == l )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+ if( !found )
+ {
+ ADMP( "^3!setlevel: ^7level is not defined\n" );
+ return qfalse;
+ }
+
+ id = G_admin_find_slot( ent, testname, "setlevel" );
+ if( id >=0 && id < MAX_CLIENTS )
+ {
+ vic = &g_entities[ id ];
+ Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) );
+ Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) );
+ }
+ else if( id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS &&
+ g_admin_admins[ id - MAX_CLIENTS ] )
+ {
+ Q_strncpyz( adminname, g_admin_admins[ id - MAX_CLIENTS ]->name,
+ sizeof( adminname ) );
+ Q_strncpyz( guid, g_admin_admins[ id - MAX_CLIENTS ]->guid,
+ sizeof( guid ) );
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+ if( !Q_stricmp( level.clients[ i ].pers.guid, guid ) )
+ {
+ vic = &g_entities[ i ];
+ Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) );
+ }
+ }
+ }
+ else
+ {
+ return qfalse;
+ }
+
+ if( !Q_stricmp( guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) )
+ {
+ ADMP( va( "^3!setlevel: ^7%s does not have a valid GUID\n", adminname ) );
+ return qfalse;
+ }
+ if( ent && !admin_higher_guid( ent->client->pers.guid, guid ) )
+ {
+ ADMP( "^3!setlevel: ^7sorry, but your intended victim has a higher"
+ " admin level than you\n" );
+ return qfalse;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ];i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) )
+ {
+ g_admin_admins[ i ]->level = l;
+ Q_strncpyz( g_admin_admins[ i ]->name, adminname,
+ sizeof( g_admin_admins[ i ]->name ) );
+ updated = qtrue;
+ }
+ }
+ if( !updated )
+ {
+ if( i == MAX_ADMIN_ADMINS )
+ {
+ ADMP( "^3!setlevel: ^7too many admins\n" );
+ return qfalse;
+ }
+ a = G_Alloc( sizeof( g_admin_admin_t ) );
+ a->level = l;
+ Q_strncpyz( a->name, adminname, sizeof( a->name ) );
+ Q_strncpyz( a->guid, guid, sizeof( a->guid ) );
+ *a->flags = '\0';
+ g_admin_admins[ i ] = a;
+ }
+
+ AP( va(
+ "print \"^3!setlevel: ^7%s^7 was given level %d admin rights by %s\n\"",
+ adminname, l, ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ if( vic )
+ {
+ vic->client->pers.adminLevel = l;
+ G_admin_set_adminname( vic );
+ }
+
+ if( !g_admin.string[ 0 ] )
+ ADMP( "^3!setlevel: ^7WARNING g_admin not set, not saving admin record "
+ "to a file\n" );
+ else
+ admin_writeconfig();
+ return qtrue;
+}
+
+static int SortFlags( const void *pa, const void *pb )
+{
+ char *a = (char *)pa;
+ char *b = (char *)pb;
+
+ if( *a == '-' || *a == '+' )
+ a++;
+ if( *b == '-' || *b == '+' )
+ b++;
+ return strcmp(a, b);
+}
+
+#define MAX_USER_FLAGS 200
+const char *G_admin_user_flag( char *oldflags, char *flag, qboolean add, qboolean clear,
+ char *newflags, int size )
+{
+ char *token, *token_p;
+ char *key;
+ char head_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ];
+ char tail_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ];
+ char allflag[ MAX_ADMIN_FLAG_LEN ];
+ char newflag[ MAX_ADMIN_FLAG_LEN ];
+ int head_count = 0;
+ int tail_count = 0;
+ qboolean flagset = qfalse;
+ int i;
+
+ if( !flag[ 0 ] )
+ {
+ return "invalid admin flag";
+ }
+
+ allflag[ 0 ] = '\0';
+ token_p = oldflags;
+ while( *( token = COM_Parse( &token_p ) ) )
+ {
+ key = token;
+ if( *key == '-' || *key == '+' )
+ key++;
+
+ if( !strcmp( key, flag ) )
+ {
+ if( flagset )
+ continue;
+ flagset = qtrue;
+ if( clear )
+ {
+ // clearing ALLFLAGS will result in clearing any following flags
+ if( !strcmp( key, ADMF_ALLFLAGS ) )
+ break;
+ else
+ continue;
+ }
+ Com_sprintf( newflag, sizeof( newflag ), "%s%s",
+ ( add ) ? "+" : "-", key );
+ }
+ else
+ {
+ Q_strncpyz( newflag, token, sizeof( newflag ) );
+ }
+
+ if( !strcmp( key, ADMF_ALLFLAGS ) )
+ {
+ if( !allflag[ 0 ] )
+ Q_strncpyz( allflag, newflag, sizeof( allflag ) );
+ continue;
+ }
+
+ if( !allflag[ 0 ] )
+ {
+ if( head_count < MAX_USER_FLAGS )
+ {
+ Q_strncpyz( head_flags[ head_count ], newflag,
+ sizeof( head_flags[ head_count ] ) );
+ head_count++;
+ }
+ }
+ else
+ {
+ if( tail_count < MAX_USER_FLAGS )
+ {
+ Q_strncpyz( tail_flags[ tail_count ], newflag,
+ sizeof( tail_flags[ tail_count ] ) );
+ tail_count++;
+ }
+ }
+ }
+
+ if( !flagset && !clear )
+ {
+ if( !strcmp( flag, ADMF_ALLFLAGS ) )
+ {
+ Com_sprintf( allflag, sizeof( allflag ), "%s%s",
+ ( add ) ? "" : "-", ADMF_ALLFLAGS );
+ }
+ else if( !allflag[ 0 ] )
+ {
+ if( head_count < MAX_USER_FLAGS )
+ {
+ Com_sprintf( head_flags[ head_count ], sizeof( head_flags[ head_count ] ),
+ "%s%s", ( add ) ? "" : "-", flag );
+ head_count++;
+ }
+ }
+ else
+ {
+ if( tail_count < MAX_USER_FLAGS )
+ {
+ Com_sprintf( tail_flags[ tail_count ], sizeof( tail_flags[ tail_count ] ),
+ "%s%s", ( add ) ? "+" : "-", flag );
+ tail_count++;
+ }
+ }
+ }
+
+ qsort( head_flags, head_count, sizeof( head_flags[ 0 ] ), SortFlags );
+ qsort( tail_flags, tail_count, sizeof( tail_flags[ 0 ] ), SortFlags );
+
+ // rebuild flags
+ newflags[ 0 ] = '\0';
+ for( i = 0; i < head_count; i++ )
+ {
+ Q_strcat( newflags, size,
+ va( "%s%s", ( i ) ? " ": "", head_flags[ i ] ) );
+ }
+ if( allflag[ 0 ] )
+ {
+ Q_strcat( newflags, size,
+ va( "%s%s", ( newflags[ 0 ] ) ? " ": "", allflag ) );
+
+ for( i = 0; i < tail_count; i++ )
+ {
+ Q_strcat( newflags, size,
+ va( " %s", tail_flags[ i ] ) );
+ }
+ }
+
+ return NULL;
+}
+
+typedef struct {
+ char *flag;
+ char *description;
+} AdminFlagListEntry_t;
+static AdminFlagListEntry_t adminFlagList[] =
+{
+ { ADMF_ACTIVITY, "inactivity rules do not apply" },
+ { ADMF_ADMINCHAT, "can see and use admin chat" },
+ { ADMF_ALLFLAGS, "has all flags and can use any command" },
+ { ADMF_BAN_IMMUNITY, "immune from IP bans" },
+ { ADMF_CAN_PERM_BAN, "can permanently ban players" },
+ { ADMF_DBUILDER, "permanent designated builder" },
+ { ADMF_FORCETEAMCHANGE, "team balance rules do not apply" },
+ { ADMF_INCOGNITO, "does not show as admin in !listplayers" },
+ { ADMF_IMMUNITY, "cannot be vote kicked or muted" },
+ { ADMF_IMMUTABLE, "admin commands cannot be used on them" },
+ { ADMF_NOCENSORFLOOD, "no flood protection" },
+ { ADMF_NO_VOTE_LIMIT, "vote limitations do not apply" },
+ { ADMF_SEESFULLLISTPLAYERS, "sees all info in !listplayers" },
+ { ADMF_SPEC_ALLCHAT, "can see team chat as spectator" },
+ { ADMF_ADMINSTEALTH, "uses admin stealth" },
+ { ADMF_TEAMCHANGEFREE, "keeps credits on team switch" },
+ { ADMF_TEAMCHAT_CMD, "can run commands from team chat" },
+ { ADMF_UNACCOUNTABLE, "does not need to specify reason for kick/ban" },
+ { ADMF_NO_BUILD, "can not build" },
+ { ADMF_NO_CHAT, "can not talk" },
+ { ADMF_NO_VOTE, "can not call votes" }
+};
+static int adminNumFlags= sizeof( adminFlagList ) / sizeof( adminFlagList[ 0 ] );
+
+#define MAX_LISTCOMMANDS 128
+qboolean G_admin_flaglist( gentity_t *ent, int skiparg )
+{
+ qboolean shown[ MAX_LISTCOMMANDS ];
+ int i, j;
+ int count = 0;
+
+ ADMBP_begin();
+
+ ADMBP( "^3Ability flags:\n" );
+
+ for( i = 0; i < adminNumFlags; i++ )
+ {
+ ADMBP( va( " %s%-20s ^7%s\n",
+ ( adminFlagList[ i ].flag[ 0 ] != '.' ) ? "^5" : "^1",
+ adminFlagList[ i ].flag,
+ adminFlagList[ i ].description ) );
+ }
+
+ ADMBP( "^3Command flags:\n" );
+
+ memset( shown, 0, sizeof( shown ) );
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( i < MAX_LISTCOMMANDS && shown[ i ] )
+ continue;
+ ADMBP( va( " ^5%-20s^7", g_admin_cmds[ i ].flag ) );
+ for( j = i; j < adminNumCmds; j++ )
+ {
+ if( !strcmp( g_admin_cmds[ j ].flag, g_admin_cmds[ i ].flag ) )
+ {
+ ADMBP( va( " %s", g_admin_cmds[ j ].keyword ) );
+ if( j < MAX_LISTCOMMANDS )
+ shown[ j ] = qtrue;
+ }
+ }
+ ADMBP( "\n" );
+ count++;
+ }
+
+ ADMBP( va( "^3!flaglist: ^7listed %d abilities and %d command flags\n",
+ adminNumFlags, count ) );
+
+ ADMBP_end();
+
+ return qtrue;
+}
+
+qboolean G_admin_flag( gentity_t *ent, int skiparg )
+{
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ char name[ MAX_NAME_LENGTH ];
+ char flagbuf[ MAX_ADMIN_FLAG_LEN ];
+ char *flag;
+ int id;
+ char adminname[ MAX_NAME_LENGTH ] = {""};
+ const char *result;
+ qboolean add = qtrue;
+ qboolean clear = qfalse;
+ int admin_level = -1;
+ int i, level;
+
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( *cmd == '!' )
+ cmd++;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( va( "^3!%s: ^7usage: !%s slot# flag\n", cmd, cmd ) );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( name[ 0 ] == '*' )
+ {
+ if( ent )
+ {
+ ADMP( va( "^3!%s: only console can change admin level flags\n", cmd ) );
+ return qfalse;
+ }
+ id = atoi( name + 1 );
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_levels[ i ]->level == id )
+ {
+ admin_level = i;
+ break;
+ }
+ }
+ if( admin_level < 0 )
+ {
+ ADMP( va( "^3!%s: admin level %d does not exist\n", cmd, id ) );
+ return qfalse;
+ }
+ Com_sprintf( adminname, sizeof( adminname ), "admin level %d", id );
+ }
+ else
+ {
+ id = G_admin_find_admin_slot( ent, name, cmd, adminname, sizeof( adminname ) );
+ if( id < 0 )
+ return qfalse;
+
+ if( ent && !admin_higher_guid( ent->client->pers.guid, g_admin_admins[ id ]->guid ) )
+ {
+ ADMP( va( "^3%s:^7 your intended victim has a higher admin level than you\n", cmd ) );
+ return qfalse;
+ }
+ }
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ flag = "";
+ level = 0;
+ if( admin_level < 0 )
+ {
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_admins[ id ]->level == g_admin_levels[ i ]->level )
+ {
+ flag = g_admin_levels[ i ]->flags;
+ level = g_admin_admins[ id ]->level;
+ break;
+ }
+ }
+ ADMP( va( "^3%s:^7 flags for %s^7 are '^3%s^7'\n",
+ cmd, adminname, g_admin_admins[ id ]->flags) );
+ }
+ else
+ {
+ flag = g_admin_levels[ admin_level ]->flags;
+ level = g_admin_levels[ admin_level ]->level;
+ }
+ ADMP( va( "^3%s:^7 level %d flags are '%s'\n",
+ cmd, level, flag ) );
+
+ return qtrue;
+ }
+
+ G_SayArgv( 2 + skiparg, flagbuf, sizeof( flagbuf ) );
+ flag = flagbuf;
+ if( flag[ 0 ] == '-' || flag[ 0 ] == '+' )
+ {
+ add = ( flag[ 0 ] == '+' );
+ flag++;
+ }
+ if( ent && !Q_stricmp( ent->client->pers.guid, g_admin_admins[ id ]->guid ) )
+ {
+ ADMP( va( "^3%s:^7 you may not change your own flags (use rcon)\n", cmd ) );
+ return qfalse;
+ }
+ if( flag[ 0 ] != '.' && !G_admin_permission( ent, flag ) )
+ {
+ ADMP( va( "^3%s:^7 you can only change flags that you also have\n", cmd ) );
+ return qfalse;
+ }
+
+ if( !Q_stricmp( cmd, "unflag" ) )
+ {
+ clear = qtrue;
+ }
+
+ if( admin_level < 0 )
+ {
+ result = G_admin_user_flag( g_admin_admins[ id ]->flags, flag, add, clear,
+ g_admin_admins[ id ]->flags, sizeof( g_admin_admins[ id ]->flags ) );
+ }
+ else
+ {
+ result = G_admin_user_flag( g_admin_levels[ admin_level ]->flags, flag, add, clear,
+ g_admin_levels[ admin_level ]->flags,
+ sizeof( g_admin_levels[ admin_level ]->flags ) );
+ }
+ if( result )
+ {
+ ADMP( va( "^3!flag: ^7an error occured setting flag '^3%s^7', %s\n",
+ flag, result ) );
+ return qfalse;
+ }
+
+ if( !Q_stricmp( cmd, "flag" ) )
+ {
+ G_AdminsPrintf( "^3!%s: ^7%s^7 was %s admin flag '%s' by %s\n",
+ cmd, adminname,
+ ( add ) ? "given" : "denied",
+ flag,
+ ( ent ) ? ent->client->pers.netname : "console" );
+ }
+ else
+ {
+ G_AdminsPrintf( "^3!%s: ^7admin flag '%s' for %s^7 cleared by %s\n",
+ cmd, flag, adminname,
+ ( ent ) ? ent->client->pers.netname : "console" );
+ }
+
+ if( !g_admin.string[ 0 ] )
+ ADMP( va( "^3!%s: ^7WARNING g_admin not set, not saving admin record "
+ "to a file\n", cmd ) );
+ else
+ admin_writeconfig();
+
+ return qtrue;
+}
+
+int G_admin_parse_time( const char *time )
+{
+ int seconds = 0, num = 0;
+ int i;
+ for( i = 0; time[ i ]; i++ )
+ {
+ if( isdigit( time[ i ] ) )
+ {
+ num = num * 10 + time[ i ] - '0';
+ continue;
+ }
+ if( i == 0 || !isdigit( time[ i - 1 ] ) )
+ return -1;
+ switch( time[ i ] )
+ {
+ case 'w': num *= 7;
+ case 'd': num *= 24;
+ case 'h': num *= 60;
+ case 'm': num *= 60;
+ case 's': break;
+ default: return -1;
+ }
+ seconds += num;
+ num = 0;
+ }
+ if( num )
+ seconds += num;
+ // overflow
+ if( seconds < 0 )
+ seconds = 0;
+ return seconds;
+}
+
+static qboolean admin_create_ban( gentity_t *ent,
+ char *netname,
+ char *guid,
+ char *ip,
+ int seconds,
+ char *reason )
+{
+ g_admin_ban_t *b = NULL;
+ qtime_t qt;
+ int t;
+ int i;
+
+ t = trap_RealTime( &qt );
+ b = G_Alloc( sizeof( g_admin_ban_t ) );
+
+ if( !b )
+ return qfalse;
+
+ Q_strncpyz( b->name, netname, sizeof( b->name ) );
+ Q_strncpyz( b->guid, guid, sizeof( b->guid ) );
+ Q_strncpyz( b->ip, ip, sizeof( b->ip ) );
+ b->suspend = 0;
+
+ //strftime( b->made, sizeof( b->made ), "%m/%d/%y %H:%M:%S", lt );
+ Q_strncpyz( b->made, va( "%02i/%02i/%02i %02i:%02i:%02i",
+ (qt.tm_mon + 1), qt.tm_mday, (qt.tm_year - 100),
+ qt.tm_hour, qt.tm_min, qt.tm_sec ),
+ sizeof( b->made ) );
+
+ Q_strncpyz( b->banner, G_admin_get_adminname( ent ), sizeof( b->banner ) );
+
+ if( ent )
+ b->bannerlevel = G_admin_level( ent );
+ else
+ b->bannerlevel = 0;
+
+ if( !seconds )
+ b->expires = 0;
+ else
+ b->expires = t + seconds;
+ if( !*reason )
+ Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) );
+ else
+ Q_strncpyz( b->reason, reason, sizeof( b->reason ) );
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ ;
+ if( i == MAX_ADMIN_BANS )
+ {
+ ADMP( "^3!ban: ^7too many bans\n" );
+ G_Free( b );
+ return qfalse;
+ }
+ g_admin_bans[ i ] = b;
+ return qtrue;
+}
+
+qboolean G_admin_kick( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
+ int minargc;
+ gentity_t *vic;
+ char notice[51];
+
+ trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) );
+
+ minargc = 3 + skiparg;
+ if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!kick: ^7usage: !kick [name] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ reason = G_SayConcatArgs( 2 + skiparg );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!kick: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( "^3!kick: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ admin_create_ban( ent,
+ vic->client->pers.netname,
+ vic->client->pers.guid,
+ vic->client->pers.ip, G_admin_parse_time( g_adminTempBan.string ),
+ ( *reason ) ? reason : "kicked by admin" );
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+
+ trap_SendServerCommand( pids[ 0 ],
+ va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"",
+ ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole",
+ ( *reason ) ? reason : "kicked by admin", notice ) );
+
+ trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s",
+ ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console",
+ ( *reason ) ? reason : "kicked by admin" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_ban( gentity_t *ent, int skiparg )
+{
+ int seconds;
+ char search[ MAX_NAME_LENGTH ];
+ char secs[ 7 ];
+ char *reason;
+ int minargc;
+ char duration[ 32 ];
+ int logmatch = -1, logmatches = 0;
+ int i, j;
+ qboolean exactmatch = qfalse;
+ char n2[ MAX_NAME_LENGTH ];
+ char s2[ MAX_NAME_LENGTH ];
+ char guid_stub[ 9 ];
+ char notice[51];
+
+ trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) );
+
+ if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ {
+ minargc = 2 + skiparg;
+ }
+ else if( ( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) || g_adminMaxBan.integer ) ||
+ G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ {
+ minargc = 3 + skiparg;
+ }
+ else
+ {
+ minargc = 4 + skiparg;
+ }
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!ban: ^7usage: !ban [name|slot|ip] [time] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, search, sizeof( search ) );
+ G_SanitiseString( search, s2, sizeof( s2 ) );
+ G_SayArgv( 2 + skiparg, secs, sizeof( secs ) );
+
+ seconds = G_admin_parse_time( secs );
+ if( seconds <= 0 )
+ {
+ if( g_adminMaxBan.integer && !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ ADMP( va( "^3!ban: ^7using your admin level's maximum ban length of %s\n",
+ g_adminMaxBan.string ) );
+ seconds = G_admin_parse_time( g_adminMaxBan.string );
+ }
+ else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ seconds = 0;
+ }
+ else
+ {
+ ADMP( "^3!ban: ^7ban time must be positive\n" );
+ return qfalse;
+ }
+ reason = G_SayConcatArgs( 2 + skiparg );
+ }
+ else
+ {
+ reason = G_SayConcatArgs( 3 + skiparg );
+
+ if( g_adminMaxBan.integer &&
+ seconds > G_admin_parse_time( g_adminMaxBan.string ) &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ seconds = G_admin_parse_time( g_adminMaxBan.string );
+ ADMP( va( "^3!ban: ^7ban length limited to %s for your admin level\n",
+ g_adminMaxBan.string ) );
+ }
+ }
+
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ // skip players in the namelog who have already been banned
+ if( g_admin_namelog[ i ]->banned )
+ continue;
+
+ // skip disconnected players when banning on slot number
+ if( g_admin_namelog[ i ]->slot == -1 )
+ continue;
+
+ if( !Q_stricmp( va( "%d", g_admin_namelog[ i ]->slot ), s2 ) )
+ {
+ logmatches = 1;
+ logmatch = i;
+ exactmatch = qtrue;
+ break;
+ }
+ }
+
+ for( i = 0;
+ !exactmatch && i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ];
+ i++ )
+ {
+ // skip players in the namelog who have already been banned
+ if( g_admin_namelog[ i ]->banned )
+ continue;
+
+ if( !Q_stricmp( g_admin_namelog[ i ]->ip, s2 ) )
+ {
+ logmatches = 1;
+ logmatch = i;
+ exactmatch = qtrue;
+ break;
+ }
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
+ && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ if( logmatch != i )
+ logmatches++;
+ logmatch = i;
+ }
+ }
+ }
+
+ if( !logmatches )
+ {
+ ADMP( "^3!ban: ^7no player found by that name, IP, or slot number\n" );
+ return qfalse;
+ }
+ else if( logmatches > 1 )
+ {
+ ADMBP_begin();
+ ADMBP( "^3!ban: ^7multiple recent clients match name, use IP or slot#:\n" );
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
+ && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ if( g_admin_namelog[ i ]->slot > -1 )
+ ADMBP( "^3" );
+ ADMBP( va( "%-2s (*%s) %15s ^7'%s^7'\n",
+ (g_admin_namelog[ i ]->slot > -1) ?
+ va( "%d", g_admin_namelog[ i ]->slot ) : "-",
+ guid_stub,
+ g_admin_namelog[ i ]->ip,
+ g_admin_namelog[ i ]->name[ j ] ) );
+ }
+ }
+ }
+ ADMBP_end();
+ return qfalse;
+ }
+
+ G_admin_duration( ( seconds ) ? seconds : -1,
+ duration, sizeof( duration ) );
+
+ if( ent && !admin_higher_guid( ent->client->pers.guid,
+ g_admin_namelog[ logmatch ]->guid ) )
+ {
+
+ ADMP( "^3!ban: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+
+ admin_create_ban( ent,
+ g_admin_namelog[ logmatch ]->name[ 0 ],
+ g_admin_namelog[ logmatch ]->guid,
+ g_admin_namelog[ logmatch ]->ip,
+ seconds, reason );
+
+ g_admin_namelog[ logmatch ]->banned = qtrue;
+
+ if( !g_admin.string[ 0 ] )
+ ADMP( "^3!ban: ^7WARNING g_admin not set, not saving ban to a file\n" );
+ else
+ admin_writeconfig();
+
+ if( g_admin_namelog[ logmatch ]->slot == -1 )
+ {
+ // client is already disconnected so stop here
+ AP( va( "print \"^3!ban:^7 %s^7 has been banned by %s^7 "
+ "duration: %s, reason: %s\n\"",
+ g_admin_namelog[ logmatch ]->name[ 0 ],
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ duration,
+ ( *reason ) ? reason : "banned by admin" ) );
+ return qtrue;
+ }
+
+ trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot,
+ va( "disconnect \"You have been banned.\n"
+ "admin:\n%s^7\nduration:\n%s\nreason:\n%s\n%s\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ duration,
+ ( *reason ) ? reason : "banned by admin", notice ) );
+
+ trap_DropClient( g_admin_namelog[ logmatch ]->slot,
+ va( "banned by %s^7, duration: %s, reason: %s",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ duration,
+ ( *reason ) ? reason : "banned by admin" ) );
+ return qtrue;
+}
+
+qboolean G_admin_adjustban( gentity_t *ent, int skiparg )
+{
+ int bnum;
+ int length;
+ int expires;
+ int time = trap_RealTime( NULL );
+ char duration[ 32 ] = {""};
+ char *reason;
+ char bs[ 5 ];
+ char secs[ MAX_TOKEN_CHARS ];
+ char mode = '\0';
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!adjustban: ^7usage: !adjustban [ban#] [time] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] )
+ {
+ ADMP( "^3!adjustban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+ if( g_admin_bans[ bnum - 1 ]->expires == 0 &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ ADMP( "^3!adjustban: ^7you cannot modify permanent bans\n" );
+ return qfalse;
+ }
+ if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) )
+ {
+ ADMP( "^3!adjustban: ^7you cannot modify Bans made by admins higher than you\n" );
+ return qfalse;
+ }
+ if( g_adminMaxBan.integer &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ g_admin_bans[ bnum - 1 ]->expires - time > G_admin_parse_time( g_adminMaxBan.string ) )
+ {
+ ADMP( va( "^3!adjustban: ^7your admin level cannot modify bans longer than %s\n",
+ g_adminMaxBan.string ) );
+ return qfalse;
+ }
+ G_SayArgv( 2 + skiparg, secs, sizeof( secs ) );
+ if( secs[ 0 ] == '+' || secs[ 0 ] == '-' )
+ mode = secs[ 0 ];
+ length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] );
+ if( length < 0 )
+ skiparg--;
+ else
+ {
+ if( length )
+ {
+ if( g_admin_bans[ bnum - 1 ]->expires == 0 && mode )
+ {
+ ADMP( "^3!adjustban: ^7new duration must be explicit\n" );
+ return qfalse;
+ }
+ if( mode == '+' )
+ expires = g_admin_bans[ bnum - 1 ]->expires + length;
+ else if( mode == '-' )
+ expires = g_admin_bans[ bnum - 1 ]->expires - length;
+ else
+ expires = time + length;
+ if( expires <= time )
+ {
+ ADMP( "^3!adjustban: ^7ban time must be positive\n" );
+ return qfalse;
+ }
+ if( g_adminMaxBan.integer &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ expires - time > G_admin_parse_time( g_adminMaxBan.string ) )
+ {
+ ADMP( va( "^3!adjustban: ^7ban length is limited to %s for your admin level\n",
+ g_adminMaxBan.string ) );
+ length = G_admin_parse_time( g_adminMaxBan.string );
+ expires = time + length;
+ }
+ }
+ else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ expires = 0;
+ else
+ {
+ ADMP( "^3!adjustban: ^7ban time must be positive\n" );
+ return qfalse;
+ }
+
+ g_admin_bans[ bnum - 1 ]->expires = expires;
+ G_admin_duration( ( expires ) ? expires - time : -1,
+ duration, sizeof( duration ) );
+ }
+ reason = G_SayConcatArgs( 3 + skiparg );
+ if( *reason )
+ Q_strncpyz( g_admin_bans[ bnum - 1 ]->reason, reason,
+ sizeof( g_admin_bans[ bnum - 1 ]->reason ) );
+ AP( va( "print \"^3!adjustban: ^7ban #%d for %s^7 has been updated by %s^7 "
+ "%s%s%s%s%s\n\"",
+ bnum,
+ g_admin_bans[ bnum - 1 ]->name,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( length >= 0 ) ? "duration: " : "",
+ duration,
+ ( length >= 0 && *reason ) ? ", " : "",
+ ( *reason ) ? "reason: " : "",
+ reason ) );
+ if( ent ) {
+ Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, G_admin_get_adminname( ent ),
+ sizeof( g_admin_bans[ bnum - 1 ]->banner ) );
+ g_admin_bans[ bnum - 1 ]->bannerlevel = G_admin_level( ent );
+ }
+
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+ return qtrue;
+}
+
+
+qboolean G_admin_subnetban( gentity_t *ent, int skiparg )
+{
+ int bnum;
+ int mask;
+ unsigned int IPRlow = 0, IPRhigh = 0;
+ char cIPRlow[ 32 ], cIPRhigh[ 32 ];
+ char bs[ 5 ];
+ char strmask[ 5 ];
+ char exl[2];
+ int k, IP[5];
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!subnetban: ^7usage: !subnetban [ban#] [mask]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] )
+ {
+ ADMP( "^3!subnetban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+ if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) )
+ {
+ ADMP( "^3!subnetban: ^7you cannot subnetban Bans on bans made by admins higher than you\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 2 + skiparg, strmask, sizeof( strmask ) );
+ mask = atoi( strmask );
+
+ if( mask >= 0 && mask <= 32)
+ {
+ G_SayArgv( 3 + skiparg, exl, sizeof( exl ) );
+ if( mask >= 0 && mask < 16 )
+ {
+ if( ent )
+ {
+ ADMP( "^3!subnetban: ^7Only console may ban such a large network. Regular admins may only ban >=16.\n" );
+ return qfalse;
+ }
+ if( strcmp(exl, "!") )
+ {
+ ADMP( "^3!subnetban: ^1WARNING:^7 you are about to ban a large network, use !subnetban [ban] [mask] ! to force^7\n" );
+ return qfalse;
+ }
+ }
+
+ memset( IP, 0, sizeof( IP ) );
+ sscanf(g_admin_bans[ bnum - 1 ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]);
+ for(k = 4; k >= 1; k--)
+ {
+ if( IP[k] )
+ IPRlow |= IP[k] << 8*(k-1);
+ }
+ IPRhigh = IPRlow;
+ if( mask == 32 )
+ {
+ Q_strncpyz(
+ g_admin_bans[ bnum - 1 ]->ip,
+ va("%i.%i.%i.%i", IP[4], IP[3], IP[2], IP[1]),
+ sizeof( g_admin_bans[ bnum - 1 ]->ip )
+ );
+ }
+ else
+ {
+ Q_strncpyz(
+ g_admin_bans[ bnum - 1 ]->ip,
+ va("%i.%i.%i.%i/%i", IP[4], IP[3], IP[2], IP[1], mask ),
+ sizeof( g_admin_bans[ bnum - 1 ]->ip )
+ );
+ IPRlow &= ~((1 << (32-mask)) - 1);
+ IPRhigh |= ((1 << (32-mask)) - 1);
+ }
+ }
+ else
+ {
+ ADMP( "^3!subnetban: ^7mask is out of range, please use 0-32 inclusive\n" );
+ return qfalse;
+ }
+ if( mask > 0 )
+ {
+ Q_strncpyz(
+ cIPRlow,
+ va("%u.%u.%u.%u", (IPRlow & (255 << 24)) >> 24, (IPRlow & (255 << 16)) >> 16, (IPRlow & (255 << 8)) >> 8, IPRlow & 255),
+ sizeof( cIPRlow )
+ );
+ Q_strncpyz(
+ cIPRhigh,
+ va("%u.%u.%u.%u", (IPRhigh & (255 << 24)) >> 24, (IPRhigh & (255 << 16)) >> 16, (IPRhigh & (255 << 8)) >> 8, IPRhigh & 255),
+ sizeof( cIPRhigh )
+ );
+ }
+ else
+ {
+ Q_strncpyz( cIPRlow, "0.0.0.0", sizeof( cIPRlow ) );
+ Q_strncpyz( cIPRhigh, "255.255.255.255", sizeof( cIPRhigh ) );
+
+ }
+
+ AP( va( "print \"^3!subnetban: ^7ban #%d for %s^7 has been updated by %s^7 "
+ "%s (%s - %s)\n\"",
+ bnum,
+ g_admin_bans[ bnum - 1 ]->name,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ g_admin_bans[ bnum - 1 ]->ip,
+ cIPRlow,
+ cIPRhigh) );
+ if( ent )
+ Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, G_admin_get_adminname( ent ),
+ sizeof( g_admin_bans[ bnum - 1 ]->banner ) );
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+ return qtrue;
+}
+
+qboolean G_admin_suspendban( gentity_t *ent, int skiparg )
+{
+ int bnum;
+ int length;
+ int timenow = 0;
+ int expires = 0;
+ char *arg;
+ char bs[ 5 ];
+ char duration[ 32 ];
+ qtime_t qt;
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!suspendban: ^7usage: !suspendban [ban #] [length]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ if( bnum < 1 || !g_admin_bans[ bnum - 1] )
+ {
+ ADMP( "^3!suspendban: ^7invalid ban #\n" );
+ return qfalse;
+ }
+ if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) )
+ {
+ ADMP( "^3!suspendban: ^7you cannot suspend Bans made by admins higher than you\n" );
+ return qfalse;
+ }
+
+ arg = G_SayConcatArgs( 2 + skiparg );
+ timenow = trap_RealTime( &qt );
+ length = G_admin_parse_time( arg );
+
+ if( length < 0 )
+ {
+ ADMP( "^3!suspendban: ^7invalid length\n" );
+ return qfalse;
+ }
+ if( length > MAX_ADMIN_BANSUSPEND_DAYS * 24 * 60 * 60 )
+ {
+ length = MAX_ADMIN_BANSUSPEND_DAYS * 24 * 60 * 60;
+ ADMP( va( "^3!suspendban: ^7maximum ban suspension is %d days\n",
+ MAX_ADMIN_BANSUSPEND_DAYS ) );
+ } else if( g_admin_bans[ bnum - 1 ]->expires > 0 && length + timenow > g_admin_bans[ bnum - 1 ]->expires ) {
+ length = g_admin_bans[ bnum - 1 ]->expires - timenow;
+ G_admin_duration( length , duration, sizeof( duration ) );
+ ADMP( va( "^3!suspendban: ^7Suspension Duration trimmed to Ban duration: %s\n",
+ duration ) );
+ }
+
+ if ( length > 0 )
+ {
+ expires = timenow + length;
+ }
+ if( g_admin_bans[ bnum - 1 ]->suspend == expires )
+ {
+ ADMP( "^3!suspendban: ^7no change\n" );
+ return qfalse;
+ }
+
+ g_admin_bans[ bnum - 1 ]->suspend = expires;
+ if ( length > 0 )
+ {
+ G_admin_duration( length , duration, sizeof( duration ) );
+ AP( va( "print \"^3!suspendban: ^7ban #%d suspended for %s\n\"",
+ bnum, duration ) );
+ }
+ else
+ {
+ AP( va( "print \"^3!suspendban: ^7ban #%d suspension removed\n\"",
+ bnum ) );
+ }
+
+ if( !g_admin.string[ 0 ] )
+ ADMP( "^3!adjustban: ^7WARNING g_admin not set, not saving ban to a file\n" );
+ else
+ admin_writeconfig();
+ return qtrue;
+}
+
+qboolean G_admin_unban( gentity_t *ent, int skiparg )
+{
+ int bnum;
+ char bs[ 5 ];
+ int t;
+
+ t = trap_RealTime( NULL );
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!unban: ^7usage: !unban [ban#]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1 ] )
+ {
+ ADMP( "^3!unban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+ if( g_admin_bans[ bnum - 1 ]->expires == 0 &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ ADMP( "^3!unban: ^7you cannot remove permanent bans\n" );
+ return qfalse;
+ }
+ if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) )
+ {
+ ADMP( "^3!unban: ^7you cannot Remove Bans made by admins higher than you\n" );
+ return qfalse;
+ }
+ if( g_adminMaxBan.integer &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ g_admin_bans[ bnum - 1 ]->expires - t > G_admin_parse_time( g_adminMaxBan.string ) )
+ {
+ ADMP( va( "^3!unban: ^7your admin level cannot remove bans longer than %s\n",
+ g_adminMaxBan.string ) );
+ return qfalse;
+ }
+ g_admin_bans[ bnum -1 ]->expires = t;
+ AP( va( "print \"^3!unban: ^7ban #%d for %s^7 has been removed by %s\n\"",
+ bnum,
+ g_admin_bans[ bnum - 1 ]->name,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+ return qtrue;
+}
+
+qboolean G_admin_putteam( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], team[ 7 ], err[ MAX_STRING_CHARS ];
+ gentity_t *vic;
+ pTeam_t teamnum = PTE_NONE;
+ char teamdesc[ 32 ] = {"spectators"};
+ char secs[ 7 ];
+ int seconds = 0;
+ qboolean useDuration = qfalse;
+
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ G_SayArgv( 2 + skiparg, team, sizeof( team ) );
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!putteam: ^7usage: !putteam [name] [h|a|s] (duration)\n" );
+ return qfalse;
+ }
+
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!putteam: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( "^3!putteam: ^7sorry, but your intended victim has a higher "
+ " admin level than you\n" );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+
+ if ( vic->client->sess.invisible == qtrue )
+ {
+ ADMP( "^3!putteam: ^7invisible players cannot join a team\n" );
+ return qfalse;
+ }
+
+ switch( team[ 0 ] )
+ {
+ case 'a':
+ teamnum = PTE_ALIENS;
+ Q_strncpyz( teamdesc, "aliens", sizeof( teamdesc ) );
+ break;
+ case 'h':
+ teamnum = PTE_HUMANS;
+ Q_strncpyz( teamdesc, "humans", sizeof( teamdesc ) );
+ break;
+ case 's':
+ teamnum = PTE_NONE;
+ break;
+ default:
+ ADMP( va( "^3!putteam: ^7unknown team %c\n", team[ 0 ] ) );
+ return qfalse;
+ }
+ //duration code
+ if( G_SayArgc() > 3 + skiparg ) {
+ //can only lock players in spectator
+ if ( teamnum != PTE_NONE )
+ {
+ ADMP( "^3!putteam: ^7You can only lock a player into the spectators team\n" );
+ return qfalse;
+ }
+ G_SayArgv( 3 + skiparg, secs, sizeof( secs ) );
+ seconds = G_admin_parse_time( secs );
+ useDuration = qtrue;
+ }
+
+ if( vic->client->pers.teamSelection == teamnum && teamnum != PTE_NONE )
+ {
+ ADMP( va( "^3!putteam: ^7%s ^7is already on the %s team\n", vic->client->pers.netname, teamdesc ) );
+ return qfalse;
+ }
+
+ if( useDuration == qtrue && seconds > 0 ) {
+ vic->client->pers.specExpires = level.time + ( seconds * 1000 );
+ }
+ G_ChangeTeam( vic, teamnum );
+
+ AP( va( "print \"^3!putteam: ^7%s^7 put %s^7 on to the %s team%s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ vic->client->pers.netname, teamdesc,
+ ( seconds ) ? va( " for %i seconds", seconds ) : "" ) );
+ return qtrue;
+}
+
+qboolean G_admin_seen(gentity_t *ent, int skiparg )
+{
+ char name[ MAX_NAME_LENGTH ];
+ char search[ MAX_NAME_LENGTH ];
+ char sduration[ 32 ];
+ qboolean numeric = qtrue;
+ int i, j;
+ int id = -1;
+ int count = 0;
+ int t;
+ qtime_t qt;
+ gentity_t *vic;
+ qboolean ison;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!seen: ^7usage: !seen [name|admin#]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ G_SanitiseString( name, search, sizeof( search ) );
+ for( i = 0; i < sizeof( search ) && search[ i ] ; i++ )
+ {
+ if( search[ i ] < '0' || search[ i ] > '9' )
+ {
+ numeric = qfalse;
+ break;
+ }
+ }
+
+ if( numeric )
+ {
+ id = atoi( name );
+ search[ 0 ] = '\0';
+ }
+
+ ADMBP_begin();
+ t = trap_RealTime( &qt );
+
+ for( i = 0; i < level.maxclients && count < 10; i ++ )
+ {
+ vic = &g_entities[ i ];
+
+ if( !vic->client || vic->client->pers.connected != CON_CONNECTED )
+ continue;
+
+ G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) );
+
+ if( i == id || (search[ 0 ] && strstr( name, search ) ) )
+ {
+ if ( vic->client->sess.invisible == qfalse )
+ {
+ ADMBP( va( "^3%4d ^7%s^7 is currently playing\n", i, vic->client->pers.netname ) );
+ count++;
+ }
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && count < 10; i++ )
+ {
+ G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) );
+ if( i + MAX_CLIENTS == id || (search[ 0 ] && strstr( name, search ) ) )
+ {
+ ison = qfalse;
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ vic = &g_entities[ j ];
+ if( !vic->client || vic->client->pers.connected != CON_CONNECTED )
+ continue;
+ G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) );
+ if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid )
+ && strstr( name, search ) )
+ {
+ if ( vic->client->sess.invisible == qfalse )
+ {
+ ison = qtrue;
+ break;
+ }
+ }
+ }
+
+ if( ison )
+ {
+ if( id == -1 )
+ continue;
+ ADMBP( va( "^3%4d ^7%s^7 is currently playing\n",
+ i + MAX_CLIENTS, g_admin_admins[ i ]->name ) );
+ }
+ else
+ {
+ G_admin_duration( t - g_admin_admins[ i ]->seen,
+ sduration, sizeof( sduration ) );
+ ADMBP( va( "%4d %s^7 last seen %s%s\n",
+ i + MAX_CLIENTS, g_admin_admins[ i ]->name ,
+ ( g_admin_admins[ i ]->seen ) ? sduration : "",
+ ( g_admin_admins[ i ]->seen ) ? " ago" : "time is unknown" ) );
+ }
+ count++;
+ }
+ }
+
+ if( search[ 0 ] )
+ ADMBP( va( "^3!seen:^7 found %d player%s matching '%s'\n",
+ count, (count == 1) ? "" : "s", search ) );
+ else if ( !count )
+ ADMBP( "^3!seen:^7 no one connectd by that slot number\n" );
+
+ ADMBP_end();
+ return qtrue;
+}
+
+void G_admin_seen_update( char *guid )
+{
+ int i;
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) )
+ {
+ qtime_t qt;
+
+ g_admin_admins[ i ]->seen = trap_RealTime( &qt );
+ return;
+ }
+ }
+}
+
+void G_admin_adminlog_cleanup( void )
+{
+ int i;
+
+ for( i = 0; i < MAX_ADMIN_ADMINLOGS && g_admin_adminlog[ i ]; i++ )
+ {
+ G_Free( g_admin_adminlog[ i ] );
+ g_admin_adminlog[ i ] = NULL;
+ }
+
+ admin_adminlog_index = 0;
+}
+
+void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skiparg, qboolean success )
+{
+ g_admin_adminlog_t *adminlog;
+ int previous;
+ int count = 1;
+ int i;
+
+ if( !command )
+ return;
+
+ if( !Q_stricmp( command, "adminlog" ) ||
+ !Q_stricmp( command, "admintest" ) ||
+ !Q_stricmp( command, "help" ) ||
+ !Q_stricmp( command, "info" ) ||
+ !Q_stricmp( command, "listadmins" ) ||
+ !Q_stricmp( command, "listplayers" ) ||
+ !Q_stricmp( command, "namelog" ) ||
+ !Q_stricmp( command, "showbans" ) ||
+ !Q_stricmp( command, "seen" ) ||
+ !Q_stricmp( command, "time" ) )
+ return;
+
+ previous = admin_adminlog_index - 1;
+ if( previous < 0 )
+ previous = MAX_ADMIN_ADMINLOGS - 1;
+
+ if( g_admin_adminlog[ previous ] )
+ count = g_admin_adminlog[ previous ]->id + 1;
+
+ if( g_admin_adminlog[ admin_adminlog_index ] )
+ adminlog = g_admin_adminlog[ admin_adminlog_index ];
+ else
+ adminlog = G_Alloc( sizeof( g_admin_adminlog_t ) );
+
+ memset( adminlog, 0, sizeof( *adminlog ) );
+ adminlog->id = count;
+ adminlog->time = level.time - level.startTime;
+ adminlog->success = success;
+ Q_strncpyz( adminlog->command, command, sizeof( adminlog->command ) );
+
+ if ( args )
+ Q_strncpyz( adminlog->args, args, sizeof( adminlog->args ) );
+ else
+ Q_strncpyz( adminlog->args, G_SayConcatArgs( 1 + skiparg ), sizeof( adminlog->args ) );
+
+ if( ent )
+ {
+ qboolean found = qfalse;
+ // real admin name
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+ Q_strncpyz( adminlog->name, g_admin_admins[ i ]->name, sizeof( adminlog->name ) );
+ found = qtrue;
+ break;
+ }
+ }
+ if( !found )
+ Q_strncpyz( adminlog->name, ent->client->pers.netname, sizeof( adminlog->name ) );
+
+ adminlog->level = ent->client->pers.adminLevel;
+ }
+ else
+ {
+ Q_strncpyz( adminlog->name, "console", sizeof( adminlog->name ) );
+ adminlog->level = 10000;
+ }
+
+ g_admin_adminlog[ admin_adminlog_index ] = adminlog;
+ admin_adminlog_index++;
+ if( admin_adminlog_index >= MAX_ADMIN_ADMINLOGS )
+ admin_adminlog_index = 0;
+}
+
+qboolean G_admin_adminlog( gentity_t *ent, int skiparg )
+{
+ g_admin_adminlog_t *results[ 10 ];
+ int result_index = 0;
+ char *search_cmd = NULL;
+ char *search_name = NULL;
+ int index;
+ int skip = 0;
+ int skipped = 0;
+ int checked = 0;
+ char n1[ MAX_NAME_LENGTH ];
+ char fmt_name[ 16 ];
+ char argbuf[ 32 ];
+ int name_length = 12;
+ int max_id = 0;
+ int i;
+ qboolean match;
+
+ memset( results, 0, sizeof( results ) );
+
+ index = admin_adminlog_index;
+ for( i = 0; i < 10; i++ )
+ {
+ int prev;
+
+ prev = index - 1;
+ if( prev < 0 )
+ prev = MAX_ADMIN_ADMINLOGS - 1;
+ if( !g_admin_adminlog[ prev ] )
+ break;
+ if( g_admin_adminlog[ prev ]->id > max_id )
+ max_id = g_admin_adminlog[ prev ]->id;
+ index = prev;
+ }
+
+ if( G_SayArgc() > 1 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, argbuf, sizeof( argbuf ) );
+ if( ( *argbuf >= '0' && *argbuf <= '9' ) || *argbuf == '-' )
+ {
+ int id;
+
+ id = atoi( argbuf );
+ if( id < 0 )
+ id += ( max_id - 9 );
+ else if( id <= max_id - MAX_ADMIN_ADMINLOGS )
+ id = max_id - MAX_ADMIN_ADMINLOGS + 1;
+
+ if( id + 9 >= max_id )
+ id = max_id - 9;
+ if( id < 1 )
+ id = 1;
+ for( i = 0; i < MAX_ADMIN_ADMINLOGS; i++ )
+ {
+ if( g_admin_adminlog[i] && g_admin_adminlog[ i ]->id == id )
+ {
+ index = i;
+ break;
+ }
+ }
+ }
+ else if ( *argbuf == '!' )
+ {
+ search_cmd = argbuf + 1;
+ }
+ else
+ {
+ search_name = argbuf;
+ }
+
+ if( G_SayArgc() > 2 + skiparg && ( search_cmd || search_name ) )
+ {
+ char skipbuf[ 4 ];
+ G_SayArgv( 2 + skiparg, skipbuf, sizeof( skipbuf ) );
+ skip = atoi( skipbuf );
+ }
+ }
+
+ if( search_cmd || search_name )
+ {
+ g_admin_adminlog_t *result_swap[ 10 ];
+
+ memset( result_swap, 0, sizeof( result_swap ) );
+
+ index = admin_adminlog_index - 1;
+ if( index < 0 )
+ index = MAX_ADMIN_ADMINLOGS - 1;
+
+ while( g_admin_adminlog[ index ] &&
+ checked < MAX_ADMIN_ADMINLOGS &&
+ result_index < 10 )
+ {
+ match = qfalse;
+ if( search_cmd )
+ {
+ if( !Q_stricmp( search_cmd, g_admin_adminlog[ index ]->command ) )
+ match = qtrue;
+ }
+ if( search_name )
+ {
+ G_SanitiseString( g_admin_adminlog[ index ]->name, n1, sizeof( n1 ) );
+ if( strstr( n1, search_name ) )
+ match = qtrue;
+ }
+
+ if( match && skip > 0 )
+ {
+ match = qfalse;
+ skip--;
+ skipped++;
+ }
+ if( match )
+ {
+ result_swap[ result_index ] = g_admin_adminlog[ index ];
+ result_index++;
+ }
+
+ checked++;
+ index--;
+ if( index < 0 )
+ index = MAX_ADMIN_ADMINLOGS - 1;
+ }
+ // search runs backwards, turn it around
+ for( i = 0; i < result_index; i++ )
+ results[ i ] = result_swap[ result_index - i - 1 ];
+ }
+ else
+ {
+ while( g_admin_adminlog[ index ] && result_index < 10 )
+ {
+ results[ result_index ] = g_admin_adminlog[ index ];
+ result_index++;
+ index++;
+ if( index >= MAX_ADMIN_ADMINLOGS )
+ index = 0;
+ }
+ }
+
+ for( i = 0; results[ i ] && i < 10; i++ )
+ {
+ int l;
+
+ G_DecolorString( results[ i ]->name, n1 );
+ l = strlen( n1 );
+ if( l > name_length )
+ name_length = l;
+ }
+ ADMBP_begin( );
+ for( i = 0; results[ i ] && i < 10; i++ )
+ {
+ char levelbuf[ 8 ];
+ int t;
+
+ t = results[ i ]->time / 1000;
+ G_DecolorString( results[ i ]->name, n1 );
+ Com_sprintf( fmt_name, sizeof( fmt_name ), "%%%ds",
+ ( name_length + strlen( results[ i ]->name ) - strlen( n1 ) ) );
+ Com_sprintf( n1, sizeof( n1 ), fmt_name, results[ i ]->name );
+ Com_sprintf( levelbuf, sizeof( levelbuf ), "%2d", results[ i ]->level );
+ ADMBP( va( "%s%3d %3d:%02d %2s ^7%s^7 %s!%s ^7%s^7\n",
+ ( results[ i ]->success ) ? "^7" : "^1",
+ results[ i ]->id,
+ t / 60, t % 60,
+ ( results[ i ]->level ) < 10000 ? levelbuf : " -",
+ n1,
+ ( results[ i ]->success ) ? "^3" : "^1",
+ results[ i ]->command,
+ results[ i ]->args ) );
+ }
+ if( search_cmd || search_name )
+ {
+ ADMBP( va( "^3!adminlog:^7 Showing %d matches for '%s^7'.",
+ result_index,
+ argbuf ) );
+ if( checked < MAX_ADMIN_ADMINLOGS && g_admin_adminlog[ checked ] )
+ ADMBP( va( " run '!adminlog %s^7 %d' to see more",
+ argbuf,
+ skipped + result_index ) );
+ ADMBP( "\n" );
+ }
+ else if ( results[ 0 ] )
+ {
+ ADMBP( va( "^3!adminlog:^7 Showing %d - %d of %d.\n",
+ results[ 0 ]->id,
+ results[ result_index - 1 ]->id,
+ max_id ) );
+ }
+ else
+ {
+ ADMBP( "^3!adminlog:^7 log is empty.\n" );
+ }
+ ADMBP_end( );
+
+ return qtrue;
+}
+
+qboolean G_admin_map( gentity_t *ent, int skiparg )
+{
+ char map[ MAX_QPATH ];
+ char layout[ MAX_QPATH ] = { "" };
+
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!map: ^7usage: !map [map] (layout)\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( skiparg + 1, map, sizeof( map ) );
+
+ if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) )
+ {
+ ADMP( va( "^3!map: ^7invalid map name '%s'\n", map ) );
+ return qfalse;
+ }
+
+ if( G_SayArgc( ) > 2 + skiparg )
+ {
+ G_SayArgv( skiparg + 2, layout, sizeof( layout ) );
+ if( !Q_stricmp( layout, "*BUILTIN*" ) ||
+ trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
+ NULL, FS_READ ) > 0 )
+ {
+ trap_Cvar_Set( "g_layouts", layout );
+ }
+ else
+ {
+ ADMP( va( "^3!map: ^7invalid layout name '%s'\n", layout ) );
+ return qfalse;
+ }
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) );
+ level.restarted = qtrue;
+ AP( va( "print \"^3!map: ^7map '%s' started by %s^7 %s\n\"", map,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) );
+ G_admin_maplog_result( "M" );
+ return qtrue;
+}
+
+qboolean G_admin_devmap( gentity_t *ent, int skiparg )
+{
+ char map[ MAX_QPATH ];
+ char layout[ MAX_QPATH ] = { "" };
+
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!devmap: ^7usage: !devmap [map] (layout)\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( skiparg + 1, map, sizeof( map ) );
+
+ if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) )
+ {
+ ADMP( va( "^3!devmap: ^7invalid map name '%s'\n", map ) );
+ return qfalse;
+ }
+
+ if( G_SayArgc( ) > 2 + skiparg )
+ {
+ G_SayArgv( skiparg + 2, layout, sizeof( layout ) );
+ if( !Q_stricmp( layout, "*BUILTIN*" ) ||
+ trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
+ NULL, FS_READ ) > 0 )
+ {
+ trap_Cvar_Set( "g_layouts", layout );
+ }
+ else
+ {
+ ADMP( va( "^3!devmap: ^7invalid layout name '%s'\n", layout ) );
+ return qfalse;
+ }
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "devmap %s", map ) );
+ level.restarted = qtrue;
+ AP( va( "print \"^3!devmap: ^7map '%s' started by %s^7 with cheats %s\n\"", map,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) );
+ G_admin_maplog_result( "D" );
+ return qtrue;
+}
+
+void G_admin_maplog_update( void )
+{
+ char map[ 64 ];
+ char maplog[ MAX_CVAR_VALUE_STRING ];
+ char *ptr;
+ int count = 0;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) );
+ ptr = maplog;
+ while( *ptr && count < MAX_ADMIN_MAPLOG_LENGTH )
+ {
+ while( *ptr != ' ' && *ptr != '\0' ) ptr++;
+
+ count++;
+ if( count >= MAX_ADMIN_MAPLOG_LENGTH )
+ {
+ *ptr = '\0';
+ }
+
+ if( *ptr == ' ' ) ptr++;
+ }
+
+ trap_Cvar_Set( "g_adminMapLog", va( "%s%s%s",
+ map,
+ ( maplog[0] != '\0' ) ? " " : "",
+ maplog ) );
+}
+
+void G_admin_maplog_result( char *flag )
+{
+ static int lastTime = 0;
+ char maplog[ MAX_CVAR_VALUE_STRING ];
+ int t;
+
+ if( !flag )
+ return;
+
+ // avoid race when called in same frame
+ if( level.time == lastTime )
+ return;
+
+ lastTime = level.time;
+
+ if( g_adminMapLog.string[ 0 ] &&
+ g_adminMapLog.string[ 1 ] == ';' )
+ {
+ // only one result allowed
+ return;
+ }
+
+ if ( level.surrenderTeam != PTE_NONE )
+ {
+ if( flag[ 0 ] == 'a' )
+ {
+ if( level.surrenderTeam == PTE_HUMANS )
+ flag = "A";
+ }
+ else if( flag[ 0 ] == 'h' )
+ {
+ if( level.surrenderTeam == PTE_ALIENS )
+ flag = "H";
+ }
+ }
+
+ t = ( level.time - level.startTime ) / 1000;
+ Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) );
+ trap_Cvar_Set( "g_adminMapLog", va( "%1s;%03d:%02d;%s",
+ flag,
+ t / 60, t % 60,
+ maplog ) );
+}
+
+
+qboolean G_admin_maplog( gentity_t *ent, int skiparg )
+{
+ char maplog[ MAX_CVAR_VALUE_STRING ];
+ char *ptr;
+ int count = 0;
+
+ Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) );
+
+ ADMBP_begin( );
+ ptr = maplog;
+ while( *ptr != '\0' && count < MAX_ADMIN_MAPLOG_LENGTH + 1 )
+ {
+ char *end;
+ const char *result = NULL;
+ char *clock = NULL;
+ char *colon;
+
+ end = ptr;
+ while( *end != ' ' && *end != '\0' ) end++;
+ if( *end == ' ' )
+ {
+ *end = '\0';
+ end++;
+ }
+
+ if( ptr[ 0 ] && ptr[ 1 ] == ';' )
+ {
+ switch( ptr[ 0 ] )
+ {
+ case 't':
+ result = "^7tie";
+ break;
+ case 'a':
+ result = "^1Alien win";
+ break;
+ case 'A':
+ result = "^1Alien win ^7/ Humans admitted defeat";
+ break;
+ case 'h':
+ result = "^4Human win";
+ break;
+ case 'H':
+ result = "^4Human win ^7/ Aliens admitted defeat";
+ break;
+ case 'd':
+ result = "^5draw vote";
+ break;
+ case 'N':
+ result = "^6admin loaded next map";
+ break;
+ case 'r':
+ result = "^2restart vote";
+ break;
+ case 'R':
+ result = "^6admin restarted map";
+ break;
+ case 'm':
+ result = "^2map vote";
+ break;
+ case 'M':
+ result = "^6admin changed map";
+ break;
+ case 'D':
+ result = "^6admin loaded devmap";
+ break;
+ default:
+ result = "";
+ break;
+ }
+ ptr += 2;
+ colon = strchr( ptr, ';' );
+ if ( colon )
+ {
+ clock = ptr;
+ ptr = colon + 1;
+ *colon = '\0';
+
+ // right justification with -6%s doesnt work..
+ if( clock[ 0 ] == '0' && clock[ 1 ] != ':' )
+ {
+ if( clock[ 1 ] == '0' && clock[ 2 ] != ':' )
+ clock[ 1 ] = ' ';
+ clock[ 0 ] = ' ';
+ }
+ }
+ }
+ else if( count == 0 )
+ {
+ result = "^3current map";
+ clock = " -:--";
+ }
+
+ ADMBP( va( "%s%20s %-6s %s^7\n",
+ ( count == 0 ) ? "^3" : "^7",
+ ptr,
+ ( clock ) ? clock : "",
+ ( result ) ? result : "" ) );
+
+ ptr = end;
+ count++;
+ }
+ ADMBP_end( );
+
+ return qtrue;
+}
+
+qboolean G_admin_layoutsave( gentity_t *ent, int skiparg )
+{
+ char layout[ MAX_QPATH ];
+
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!layoutsave: ^7usage: !layoutsave [layout]\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( skiparg + 1, layout, sizeof( layout ) );
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "layoutsave %s", layout ) );
+ AP( va( "print \"^3!layoutsave: ^7layout saved as '%s' by %s\n\"", layout,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_demo( gentity_t *ent, int skiparg )
+{
+ if( !ent )
+ {
+ ADMP( "!demo: console can not use demo.\n" );
+ return qfalse;
+ }
+
+ ent->client->pers.ignoreAdminWarnings = !( ent->client->pers.ignoreAdminWarnings );
+
+ ADMP( va( "^3!demo: ^7your visibility of admin chat is now %s\n",
+ ( ent->client->pers.ignoreAdminWarnings ) ? "^1disabled" : "^2enabled" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_mute( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ gentity_t *vic;
+ char secs[ 7 ];
+ int seconds = 0;
+
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+
+ if( cmd && *cmd == '!' )
+ cmd++;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( va( "^3!%s: ^7usage: !%s [name|slot#] (duration)\n", cmd, cmd ) );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!%s: ^7%s\n", cmd, err ) );
+ return qfalse;
+ }
+
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", cmd ) );
+ return qfalse;
+ }
+
+ vic = &g_entities[ pids[ 0 ] ];
+ if( !Q_stricmp( cmd, "unmute" ) )
+ {
+ if( vic->client->pers.muted == qfalse )
+ {
+ ADMP( "^3!unmute: ^7player is not currently muted\n" );
+ return qtrue;
+ }
+
+ vic->client->pers.muteExpires = 0;
+ vic->client->pers.muted = qfalse;
+
+ CPx( pids[ 0 ], "cp \"^1You have been unmuted\"" );
+ AP( va( "print \"^3!unmute: ^7%s^7 has been unmuted by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ } else {
+ // Duration
+ if( G_SayArgc() > 2 + skiparg )
+ {
+ G_SayArgv( 2 + skiparg, secs, sizeof( secs ) );
+ seconds = G_admin_parse_time( secs );
+ vic->client->pers.muteExpires = level.time + ( seconds * 1000 );
+ }
+
+ vic->client->pers.muted = qtrue;
+
+ CPx( pids[ 0 ], "cp \"^1You've been muted\"" );
+ AP( va( "print \"^3!mute: ^7%s^7 has been muted by ^7%s%s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( seconds ) ? va( " ^7for %i seconds", seconds ) : "" ) );
+ }
+
+ return qtrue;
+}
+
+qboolean G_admin_cp( gentity_t *ent, int skiparg )
+{
+ int minargc;
+ char *s;
+
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!cp: ^7usage: !cp [message]\n" );
+ return qfalse;
+ }
+
+ s = G_SayConcatArgs( 1 + skiparg );
+ G_CP(ent);
+ return qtrue;
+}
+
+qboolean G_admin_denybuild( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ gentity_t *vic;
+
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( cmd && *cmd == '!' )
+ cmd++;
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( va( "^3!%s: ^7usage: !%s [name|slot#]\n", cmd, cmd ) );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!%s: ^7%s\n", cmd, err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", cmd ) );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ if( vic->client->pers.denyBuild )
+ {
+ if( !Q_stricmp( cmd, "denybuild" ) )
+ {
+ ADMP( "^3!denybuild: ^7player already has no building rights\n" );
+ return qtrue;
+ }
+ vic->client->pers.denyBuild = qfalse;
+ CPx( pids[ 0 ], "cp \"^1You've regained your building rights\"" );
+ AP( va(
+ "print \"^3!allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ else
+ {
+ if( !Q_stricmp( cmd, "allowbuild" ) )
+ {
+ ADMP( "^3!allowbuild: ^7player already has building rights\n" );
+ return qtrue;
+ }
+ vic->client->pers.denyBuild = qtrue;
+ vic->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
+ if( vic->client->ps.stats[ STAT_PCLASS ]== PCL_ALIEN_BUILDER0 || vic->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG )
+ {
+ vic->suicideTime = level.time + 1000;
+ }
+ CPx( pids[ 0 ], "cp \"^1You've lost your building rights\"" );
+ AP( va(
+ "print \"^3!denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ return qtrue;
+}
+
+qboolean G_admin_denyweapon( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ char buffer[ 32 ];
+ int weapon = WP_NONE;
+ int class = PCL_NONE;
+ char *realname;
+ gentity_t *vic;
+ int flag;
+ qboolean all = qfalse;
+
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( cmd && *cmd == '!' )
+ cmd++;
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( va( "^3!%s: ^7usage: !%s [name|slot#] [class|weapon]\n", cmd, cmd ) );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!%s: ^7%s\n", cmd, err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", cmd ) );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+
+ if( vic == ent &&
+ !Q_stricmp( cmd, "denyweapon" ) )
+ {
+ ADMP( va( "^3!%s: ^7sorry, you cannot %s yourself\n", cmd, cmd ) );
+ return qfalse;
+ }
+
+ G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) );
+
+ if( !Q_stricmp( buffer, "all" ) &&
+ !Q_stricmp( cmd, "allowweapon" ) )
+ {
+ if( vic->client->pers.denyHumanWeapons ||
+ vic->client->pers.denyAlienClasses )
+ {
+ vic->client->pers.denyHumanWeapons = 0;
+ vic->client->pers.denyAlienClasses = 0;
+
+ CPx( pids[ 0 ], "cp \"^1You've regained all weapon and class rights\"" );
+ AP( va( "print \"^3!%s: ^7all weapon and class rights for ^7%s^7 restored by %s\n\"",
+ cmd,
+ vic->client->pers.netname,
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+ else
+ {
+ ADMP( va( "^3!%s: ^7player already has all rights\n", cmd ) );
+ }
+ return qtrue;
+ }
+
+ if( !Q_stricmp( buffer, "all" ) &&
+ !Q_stricmp( cmd, "denyweapon" ) )
+ {
+ all = qtrue;
+ weapon = WP_NONE;
+ class = PCL_NONE;
+
+ if( vic->client->pers.denyHumanWeapons == 0xFFFFFF &&
+ vic->client->pers.denyAlienClasses == 0xFFFFFF )
+ {
+ ADMP( va( "^3!%s: ^7player already has no weapon or class rights\n", cmd ) );
+ return qtrue;
+ }
+
+ if( vic->client->pers.teamSelection == PTE_HUMANS )
+ {
+ weapon = vic->client->ps.weapon;
+ if( weapon < WP_PAIN_SAW || weapon > WP_GRENADE )
+ weapon = WP_NONE;
+ }
+ if( vic->client->pers.teamSelection == PTE_ALIENS )
+ {
+ class = vic->client->pers.classSelection;
+ if( class < PCL_ALIEN_LEVEL1 || class > PCL_ALIEN_LEVEL4 )
+ class = PCL_NONE;
+ }
+
+ vic->client->pers.denyHumanWeapons = 0xFFFFFF;
+ vic->client->pers.denyAlienClasses = 0xFFFFFF;
+ }
+ else
+ {
+ weapon = BG_FindWeaponNumForName( buffer );
+ if( weapon < WP_PAIN_SAW || weapon > WP_GRENADE )
+ class = BG_FindClassNumForName( buffer );
+ if( ( weapon < WP_PAIN_SAW || weapon > WP_GRENADE ) &&
+ ( class < PCL_ALIEN_LEVEL1 || class > PCL_ALIEN_LEVEL4 ) )
+ {
+ {
+ ADMP( va( "^3!%s: ^7unknown weapon or class\n", cmd ) );
+ return qfalse;
+ }
+ }
+ }
+
+ if( class == PCL_NONE )
+ {
+ realname = BG_FindHumanNameForWeapon( weapon );
+ flag = 1 << (weapon - WP_BLASTER);
+ if( !Q_stricmp( cmd, "denyweapon" ) )
+ {
+ if( ( vic->client->pers.denyHumanWeapons & flag ) && !all )
+ {
+ ADMP( va( "^3!%s: ^7player already has no %s rights\n", cmd, realname ) );
+ return qtrue;
+ }
+ vic->client->pers.denyHumanWeapons |= flag;
+ if( vic->client->pers.teamSelection == PTE_HUMANS )
+ {
+ if( ( weapon == WP_GRENADE || all ) &&
+ BG_InventoryContainsUpgrade( UP_GRENADE, vic->client->ps.stats ) )
+ {
+ BG_RemoveUpgradeFromInventory( UP_GRENADE, vic->client->ps.stats );
+ G_AddCreditToClient( vic->client, (short)BG_FindPriceForUpgrade( UP_GRENADE ), qfalse );
+ }
+ if( BG_InventoryContainsWeapon( weapon, vic->client->ps.stats ) )
+ {
+ int maxAmmo, maxClips;
+
+ BG_RemoveWeaponFromInventory( weapon, vic->client->ps.stats );
+ G_AddCreditToClient( vic->client, (short)BG_FindPriceForWeapon( weapon ), qfalse );
+
+ BG_AddWeaponToInventory( WP_MACHINEGUN, vic->client->ps.stats );
+ BG_FindAmmoForWeapon( WP_MACHINEGUN, &maxAmmo, &maxClips );
+ vic->client->ps.ammo = maxAmmo;
+ vic->client->ps.clips = maxClips;
+ G_ForceWeaponChange( vic, WP_MACHINEGUN );
+ vic->client->ps.stats[ STAT_MISC ] = 0;
+ ClientUserinfoChanged( pids[ 0 ], qfalse );
+ }
+ }
+ }
+ else
+ {
+ if( !( vic->client->pers.denyHumanWeapons & flag ) )
+ {
+ ADMP( va( "^3!%s: ^7player already has %s rights\n", cmd, realname ) );
+ return qtrue;
+ }
+ vic->client->pers.denyHumanWeapons &= ~flag;
+ }
+ }
+ else
+ {
+ realname = BG_FindHumanNameForClassNum( class );
+ flag = 1 << class;
+ if( !Q_stricmp( cmd, "denyweapon" ) )
+ {
+ if( ( vic->client->pers.denyAlienClasses & flag ) && !all )
+ {
+ ADMP( va( "^3!%s: ^7player already has no %s rights\n", cmd, realname ) );
+ return qtrue;
+ }
+ vic->client->pers.denyAlienClasses |= flag;
+ if( vic->client->pers.teamSelection == PTE_ALIENS &&
+ vic->client->pers.classSelection == class )
+ {
+ vec3_t infestOrigin;
+ short cost;
+
+ G_RoomForClassChange( vic, PCL_ALIEN_LEVEL0, infestOrigin );
+
+ vic->client->pers.evolveHealthFraction = (float)vic->client->ps.stats[ STAT_HEALTH ] /
+ (float)BG_FindHealthForClass( class );
+ if( vic->client->pers.evolveHealthFraction < 0.0f )
+ vic->client->pers.evolveHealthFraction = 0.0f;
+ else if( vic->client->pers.evolveHealthFraction > 1.0f )
+ vic->client->pers.evolveHealthFraction = 1.0f;
+
+ vic->client->pers.classSelection = PCL_ALIEN_LEVEL0;
+ cost = BG_ClassCanEvolveFromTo( PCL_ALIEN_LEVEL0, class, 9, 0 );
+ if( cost < 0 ) cost = 0;
+ G_AddCreditToClient( vic->client, cost, qfalse );
+ ClientUserinfoChanged( pids[ 0 ], qfalse );
+ VectorCopy( infestOrigin, vic->s.pos.trBase );
+ ClientSpawn( vic, vic, vic->s.pos.trBase, vic->s.apos.trBase );
+ }
+ }
+ else
+ {
+ if( !( vic->client->pers.denyAlienClasses & flag ) )
+ {
+ ADMP( va( "^3!%s: ^7player already has %s rights\n", cmd, realname ) );
+ return qtrue;
+ }
+ vic->client->pers.denyAlienClasses &= ~flag;
+ }
+ }
+
+ if( all )
+ realname = "weapon and class";
+
+ CPx( pids[ 0 ], va( "cp \"^1You've %s your %s rights\"",
+ ( !Q_stricmp( cmd, "denyweapon" ) ) ? "lost" : "regained",
+ realname ) );
+ AP( va(
+ "print \"^3!%s: ^7%s rights for ^7%s^7 %s by %s\n\"",
+ cmd, realname,
+ vic->client->pers.netname,
+ ( !Q_stricmp( cmd, "denyweapon" ) ) ? "revoked" : "restored",
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_listadmins( gentity_t *ent, int skiparg )
+{
+ int i, found = 0;
+ char search[ MAX_NAME_LENGTH ] = {""};
+ char s[ MAX_NAME_LENGTH ] = {""};
+ int start = 0;
+ qboolean numeric = qtrue;
+ int drawn = 0;
+ int minlevel = 1;
+
+ if( G_SayArgc() == 3 + skiparg )
+ {
+ G_SayArgv( 2 + skiparg, s, sizeof( s ) );
+ if( s[ 0 ] >= '0' && s[ 0] <= '9' )
+ {
+ minlevel = atoi( s );
+ if( minlevel < 1 )
+ minlevel = 1;
+ }
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( g_admin_admins[ i ]->level >= minlevel )
+ found++;
+ }
+ if( !found )
+ {
+ if( minlevel > 1 )
+ {
+ ADMP( va( "^3!listadmins: ^7no admins level %i or greater found\n", minlevel ) );
+ }
+ else
+ {
+ ADMP( "^3!listadmins: ^7no admins defined\n" );
+ }
+ return qfalse;
+ }
+
+ if( G_SayArgc() >= 2 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, s, sizeof( s ) );
+ for( i = 0; i < sizeof( s ) && s[ i ]; i++ )
+ {
+ if( s[ i ] >= '0' && s[ i ] <= '9' )
+ continue;
+ numeric = qfalse;
+ }
+ if( numeric )
+ {
+ start = atoi( s );
+ if( start > 0 )
+ start -= 1;
+ else if( start < 0 )
+ start = found + start;
+ }
+ else
+ G_SanitiseString( s, search, sizeof( search ) );
+ }
+
+ if( start >= found || start < 0 )
+ start = 0;
+
+ drawn = admin_listadmins( ent, start, search, minlevel );
+
+ if( search[ 0 ] )
+ {
+ if( drawn <= 20 )
+ {
+ ADMP( va( "^3!listadmins:^7 found %d admins level %i or greater matching '%s^7'\n",
+ drawn, minlevel, search ) );
+ }
+ else
+ {
+ ADMP( va( "^3!listadmins:^7 found >20 admins level %i or greater matching '%s^7. Try a more narrow search.'\n",
+ minlevel, search ) );
+ }
+ }
+ else
+ {
+ ADMBP_begin();
+ ADMBP( va( "^3!listadmins:^7 showing admins level %i or greater %d - %d of %d. ",
+ minlevel,
+ ( found ) ? ( start + 1 ) : 0,
+ ( ( start + MAX_ADMIN_LISTITEMS ) > found ) ?
+ found : ( start + MAX_ADMIN_LISTITEMS ),
+ found ) );
+ if( ( start + MAX_ADMIN_LISTITEMS ) < found )
+ {
+ if( minlevel > 1)
+ {
+ ADMBP( va( "run '!listadmins %d %d' to see more",
+ ( start + MAX_ADMIN_LISTITEMS + 1 ), minlevel ) );
+ }
+ else
+ {
+ ADMBP( va( "run '!listadmins %d' to see more",
+ ( start + MAX_ADMIN_LISTITEMS + 1 ) ) );
+ }
+ }
+ ADMBP( "\n" );
+ ADMBP_end();
+ }
+ return qtrue;
+}
+
+qboolean G_admin_listlayouts( gentity_t *ent, int skiparg )
+{
+ char list[ MAX_CVAR_VALUE_STRING ];
+ char map[ MAX_QPATH ];
+ int count = 0;
+ char *s;
+ char layout[ MAX_QPATH ] = { "" };
+ int i = 0;
+
+ if( G_SayArgc( ) == 2 + skiparg )
+ G_SayArgv( 1 +skiparg, map, sizeof( map ) );
+ else
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ count = G_LayoutList( map, list, sizeof( list ) );
+ ADMBP_begin( );
+ ADMBP( va( "^3!listlayouts:^7 %d layouts found for '%s':\n", count, map ) );
+ s = &list[ 0 ];
+ while( *s )
+ {
+ if( *s == ' ' )
+ {
+ ADMBP( va ( " %s\n", layout ) );
+ layout[ 0 ] = '\0';
+ i = 0;
+ }
+ else if( i < sizeof( layout ) - 2 )
+ {
+ layout[ i++ ] = *s;
+ layout[ i ] = '\0';
+ }
+ s++;
+ }
+ if( layout[ 0 ] )
+ ADMBP( va ( " %s\n", layout ) );
+ ADMBP_end( );
+ return qtrue;
+}
+
+qboolean G_admin_listplayers( gentity_t *ent, int skiparg )
+{
+ int i, j;
+ int invisiblePlayers = 0;
+ gclient_t *p;
+ char c[ 3 ], t[ 2 ]; // color and team letter
+ char n[ MAX_NAME_LENGTH ] = {""};
+ char n2[ MAX_NAME_LENGTH ] = {""};
+ char n3[ MAX_NAME_LENGTH ] = {""};
+ char lname[ MAX_NAME_LENGTH ];
+ char lname2[ MAX_NAME_LENGTH ];
+ char guid_stub[ 9 ];
+ char muted[ 2 ], denied[ 2 ], dbuilder[ 2 ], misc[ 2 ];
+ int l;
+ char lname_fmt[ 5 ];
+
+ for( i = 0; i < level.maxclients; i++ ) {
+ p = &level.clients[ i ];
+ if ( p->sess.invisible == qtrue )
+ invisiblePlayers++;
+ }
+
+ ADMBP_begin();
+ ADMBP( va( "^3!listplayers^7: %i players connected:\n",
+ level.numConnectedClients - invisiblePlayers ) );
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ p = &level.clients[ i ];
+
+ // Ignore invisible players
+ if ( p->sess.invisible == qtrue )
+ continue;
+
+ Q_strncpyz( t, "S", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_YELLOW, sizeof( c ) );
+ if( p->pers.teamSelection == PTE_HUMANS )
+ {
+ Q_strncpyz( t, "H", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_BLUE, sizeof( c ) );
+ }
+ else if( p->pers.teamSelection == PTE_ALIENS )
+ {
+ Q_strncpyz( t, "A", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_RED, sizeof( c ) );
+ }
+
+ if( p->pers.connected == CON_CONNECTING )
+ {
+ Q_strncpyz( t, "C", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_CYAN, sizeof( c ) );
+ }
+ else if( p->pers.connected != CON_CONNECTED )
+ {
+ continue;
+ }
+
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = p->pers.guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+
+ muted[ 0 ] = '\0';
+ if( p->pers.muted )
+ {
+ Q_strncpyz( muted, "M", sizeof( muted ) );
+ }
+ denied[ 0 ] = '\0';
+ if( p->pers.denyBuild )
+ {
+ Q_strncpyz( denied, "B", sizeof( denied ) );
+ }
+ if( p->pers.denyHumanWeapons || p->pers.denyAlienClasses )
+ {
+ Q_strncpyz( denied, "W", sizeof( denied ) );
+ }
+
+ dbuilder[ 0 ] = '\0';
+ if( p->pers.designatedBuilder )
+ {
+ if( !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) &&
+ G_admin_permission( &g_entities[ i ], ADMF_DBUILDER ) &&
+ G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) )
+ {
+ Q_strncpyz( dbuilder, "P", sizeof( dbuilder ) );
+ }
+ else
+ {
+ Q_strncpyz( dbuilder, "D", sizeof( dbuilder ) );
+ }
+ }
+
+ misc[ 0 ] = '\0';
+ if( G_admin_permission( &g_entities[ i ], ADMF_BAN_IMMUNITY ) )
+ {
+ // use Misc slot for Immunity player status
+ Q_strncpyz( misc, "I", sizeof( misc ) );
+ } else if( p->pers.paused ) {
+ // use Misc slot for paused player status
+ Q_strncpyz( misc, "P", sizeof( misc ) );
+ }
+
+ l = 0;
+ G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) );
+ n[ 0 ] = '\0';
+ for( j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ j ]->guid, p->pers.guid ) )
+ {
+
+ // don't gather aka or level info if the admin is incognito
+ if( ent && G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) )
+ {
+ break;
+ }
+ l = g_admin_admins[ j ]->level;
+ G_SanitiseString( g_admin_admins[ j ]->name, n3, sizeof( n3 ) );
+ if( Q_stricmp( n2, n3 ) )
+ {
+ Q_strncpyz( n, g_admin_admins[ j ]->name, sizeof( n ) );
+ }
+ break;
+ }
+ }
+ lname[ 0 ] = '\0';
+ Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == l )
+ {
+ Q_strncpyz( lname, g_admin_levels[ j ]->name, sizeof( lname ) );
+ if( *lname )
+ {
+ G_DecolorString( lname, lname2 );
+ Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
+ ( admin_level_maxname + strlen( lname ) - strlen( lname2 ) ) );
+ Com_sprintf( lname2, sizeof( lname2 ), lname_fmt, lname );
+ }
+ break;
+ }
+
+ }
+
+ if( G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) {
+
+ ADMBP( va( "%2i %s%s^7 %-2i %s^7 (*%s) ^1%1s%1s%1s%1s^7 %s^7 %s%s^7%s\n",
+ i,
+ c,
+ t,
+ l,
+ ( *lname ) ? lname2 : "",
+ guid_stub,
+ muted,
+ dbuilder,
+ denied,
+ misc,
+ p->pers.netname,
+ ( *n ) ? "(a.k.a. " : "",
+ n,
+ ( *n ) ? ")" : ""
+ ) );
+ }
+ else
+ {
+ ADMBP( va( "%2i %s%s^7 ^1%1s%1s%1s^7 %s^7\n",
+ i,
+ c,
+ t,
+ muted,
+ dbuilder,
+ denied,
+ p->pers.netname
+ ) );
+ }
+ }
+ ADMBP_end();
+ return qtrue;
+}
+
+#define MAX_LISTMAPS_MAPS 256
+
+static int SortMaps(const void *a, const void *b)
+{
+ return strcmp(*(char **)a, *(char **)b);
+}
+
+qboolean G_admin_listmaps( gentity_t *ent, int skiparg )
+{
+ char fileList[ 4096 ] = {""};
+ char *fileSort[ MAX_LISTMAPS_MAPS ];
+ char search[ 16 ] = {""};
+ int numFiles;
+ int i;
+ int fileLen = 0;
+ int count = 0;
+ char *filePtr;
+ int rows;
+
+ if( G_SayArgc( ) > 1 + skiparg )
+ {
+ G_SayArgv( skiparg + 1, search, sizeof( search ) );
+ }
+
+ numFiles = trap_FS_GetFileList( "maps/", ".bsp",
+ fileList, sizeof( fileList ) );
+ filePtr = fileList;
+ for( i = 0; i < numFiles && count < MAX_LISTMAPS_MAPS; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ if (fileLen < 5)
+ continue;
+
+ filePtr[ fileLen - 4 ] = '\0';
+
+ if( search[ 0 ] && !strstr( filePtr, search ) )
+ continue;
+
+ fileSort[ count ] = filePtr;
+ count++;
+ }
+
+ qsort(fileSort, count, sizeof(fileSort[ 0 ]), SortMaps);
+
+ rows = count / 3;
+ if ( rows * 3 < count ) rows++;
+
+ ADMBP_begin();
+ for( i = 0; i < rows; i++ )
+ {
+ ADMBP( va( "^7%20s %20s %20s\n",
+ fileSort[ i ],
+ ( rows + i < count ) ? fileSort[ rows + i ] : "",
+ ( rows * 2 + i < count ) ? fileSort[ rows * 2 + i ] : "" ) );
+ }
+ if ( search[ 0 ] )
+ ADMBP( va( "^3!listmaps: ^7found %d maps matching '%s^7'.\n", count, search ) );
+ else
+ ADMBP( va( "^3!listmaps: ^7listing %d maps.\n", count ) );
+
+ ADMBP_end();
+
+ return qtrue;
+}
+
+qboolean G_admin_listrotation( gentity_t *ent, int skiparg )
+{
+ int i, j, statusColor;
+ char mapnames[ MAX_STRING_CHARS ];
+ char *status = NULL;
+
+ extern mapRotations_t mapRotations;
+
+ // Check for an active map rotation
+ if ( !G_MapRotationActive() ||
+ g_currentMapRotation.integer == NOT_ROTATING )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"^3!rotation: ^7There is no active map rotation on this server\n\"" );
+ return qfalse;
+ }
+
+ // Locate the active map rotation and output its contents
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ if ( i == g_currentMapRotation.integer )
+ {
+ int currentMap = G_GetCurrentMap( i );
+
+ ADMBP_begin();
+ ADMBP( va( "^3!rotation: ^7%s\n", mapRotations.rotations[ i ].name ) );
+
+ for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ )
+ {
+ Q_strncpyz( mapnames, mapRotations.rotations[ i ].maps[ j ].name, sizeof( mapnames ) );
+
+ if( !Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*VOTE*" ) )
+ {
+ char slotMap[ 64 ];
+ int lineLen = 0;
+ int k;
+
+ trap_Cvar_VariableStringBuffer( "mapname", slotMap, sizeof( slotMap ) );
+ mapnames[ 0 ] = '\0';
+ for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ )
+ {
+ char *thisMap;
+ int mc = 7;
+
+ if( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].lhs != MCV_VOTE )
+ continue;
+
+ thisMap = mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest;
+ lineLen += strlen( thisMap ) + 1;
+
+ if( currentMap == j && !Q_stricmp( thisMap, slotMap ) )
+ mc = 3;
+ Q_strcat( mapnames, sizeof( mapnames ), va( "^7%s%s^%i%s",
+ ( k ) ? ", " : "",
+ ( lineLen > 50 ) ? "\n " : "",
+ mc, thisMap ) );
+ if( lineLen > 50 )
+ lineLen = strlen( thisMap ) + 2;
+ else
+ lineLen++;
+ }
+
+ if( currentMap == j )
+ {
+ statusColor = 3;
+ status = "current slot";
+ }
+ else if( !k )
+ {
+ statusColor = 1;
+ status = "empty vote";
+ }
+ else
+ {
+ statusColor = 7;
+ status = "vote";
+ }
+ } else if( !Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*" ) )
+ {
+ char slotMap[ 64 ];
+ int lineLen = 0;
+ int k;
+
+ trap_Cvar_VariableStringBuffer( "mapname", slotMap, sizeof( slotMap ) );
+ mapnames[ 0 ] = '\0';
+ for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ )
+ {
+ char *thisMap;
+ int mc = 7;
+
+ if( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].lhs != MCV_SELECTEDRANDOM )
+ continue;
+
+ thisMap = mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest;
+ lineLen += strlen( thisMap ) + 1;
+
+ if( currentMap == j && !Q_stricmp( thisMap, slotMap ) )
+ mc = 3;
+ Q_strcat( mapnames, sizeof( mapnames ), va( "^7%s%s^%i%s",
+ ( k ) ? ", " : "",
+ ( lineLen > 50 ) ? "\n " : "",
+ mc, thisMap ) );
+ if( lineLen > 50 )
+ lineLen = strlen( thisMap ) + 2;
+ else
+ lineLen++;
+ }
+
+ if( currentMap == j )
+ {
+ statusColor = 3;
+ status = "current slot";
+ }
+ else if( !k )
+ {
+ statusColor = 1;
+ status = "empty Random";
+ }
+ else
+ {
+ statusColor = 7;
+ status = "Random";
+ }
+ }
+ else if ( currentMap == j )
+ {
+ statusColor = 3;
+ status = "current slot";
+ }
+ else if ( !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) )
+ {
+ statusColor = 1;
+ status = "missing";
+ }
+ else
+ {
+ statusColor = 7;
+ status = "";
+ }
+ ADMBP( va( " ^%i%-12s %3i %s\n", statusColor, status, j + 1, mapnames ) );
+ }
+
+ ADMBP_end();
+
+ // No maps were found in the active map rotation
+ if ( mapRotations.rotations[ i ].numMaps < 1 )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \" - ^7Empty!\n\"" );
+ return qfalse;
+ }
+ }
+ }
+
+ if( g_nextMap.string[0] )
+ {
+ ADMP( va ("^5 Next map overriden by g_nextMap to: %s\n", g_nextMap.string ) );
+ }
+
+ return qtrue;
+}
+
+
+qboolean G_admin_showbans( gentity_t *ent, int skiparg )
+{
+ int i, found = 0;
+ int t;
+ char duration[ 32 ];
+ char sduration[ 32 ];
+ char suspended[ 64 ] = { "" };
+ char name_fmt[ 32 ] = { "%s" };
+ char banner_fmt[ 32 ] = { "%s" };
+ int max_name = 1, max_banner = 1;
+ int secs;
+ int start = 0;
+ char filter[ MAX_NAME_LENGTH ] = {""};
+ char date[ 11 ];
+ char *made;
+ int j;
+ char n1[ MAX_NAME_LENGTH * 2 ] = {""};
+ char n2[ MAX_NAME_LENGTH * 2 ] = {""};
+ int bannerslevel = 0;
+ qboolean numeric = qtrue;
+ char *ip_match = NULL;
+ int ip_match_len = 0;
+ char name_match[ MAX_NAME_LENGTH ] = {""};
+ int show_count = 0;
+ qboolean subnetfilter = qfalse;
+
+ t = trap_RealTime( NULL );
+
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ if( g_admin_bans[ i ]->expires != 0
+ && ( g_admin_bans[ i ]->expires - t ) < 1 )
+ {
+ continue;
+ }
+ found++;
+ }
+
+ if( G_SayArgc() >= 2 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, filter, sizeof( filter ) );
+ if( G_SayArgc() >= 3 + skiparg )
+ {
+ start = atoi( filter );
+ G_SayArgv( 2 + skiparg, filter, sizeof( filter ) );
+ }
+ for( i = 0; i < sizeof( filter ) && filter[ i ] ; i++ )
+ {
+ if( ( filter[ i ] < '0' || filter[ i ] > '9' )
+ && filter[ i ] != '.' && filter[ i ] != '-' )
+ {
+ numeric = qfalse;
+ break;
+ }
+ }
+
+ if (!numeric)
+ {
+ if( filter[ 0 ] != '-' )
+ {
+ G_SanitiseString( filter, name_match, sizeof( name_match) );
+
+ }
+ else
+ {
+ if( !Q_strncmp( filter, "-sub", 4 ) )
+ {
+ subnetfilter = qtrue;
+ }
+ else
+ {
+ ADMP( va( "^3!showbans: ^7invalid argument %s\n", filter ) );
+ return qfalse;
+ }
+ }
+ }
+ else if( strchr( filter, '.' ) != NULL )
+ {
+ ip_match = filter;
+ ip_match_len = strlen(ip_match);
+ }
+ else
+ {
+ start = atoi( filter );
+ filter[0] = '\0';
+ }
+ // showbans 1 means start with ban 0
+ if( start > 0 )
+ start -= 1;
+ else if( start < 0 )
+ start = found + start;
+ }
+
+ if( start >= MAX_ADMIN_BANS || start < 0 )
+ start = 0;
+
+ for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ]
+ && show_count < MAX_ADMIN_SHOWBANS; i++ )
+ {
+ qboolean match = qfalse;
+
+ if (!numeric)
+ {
+ if( !subnetfilter )
+ {
+ G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) );
+ if (strstr( n1, name_match) )
+ match = qtrue;
+ }
+ else
+ {
+ int mask = -1;
+ int dummy;
+ int scanflen = 0;
+ scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask );
+ if( scanflen == 5 && mask < 32 )
+ {
+ match = qtrue;
+ }
+ }
+ }
+
+ if ( ( match ) || !ip_match
+ || Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len) == 0 )
+ {
+ G_DecolorString( g_admin_bans[ i ]->name, n1 );
+ G_DecolorString( g_admin_bans[ i ]->banner, n2 );
+ if( strlen( n1 ) > max_name )
+ {
+ max_name = strlen( n1 );
+ }
+ if( strlen( n2 ) > max_banner )
+ max_banner = strlen( n2 );
+
+ show_count++;
+ }
+ }
+
+ if( start >= found )
+ {
+ ADMP( va( "^3!showbans: ^7there are %d active bans\n", found ) );
+ return qfalse;
+ }
+ ADMBP_begin();
+ show_count = 0;
+ for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ]
+ && show_count < MAX_ADMIN_SHOWBANS; i++ )
+ {
+ if (!numeric)
+ {
+ if( !subnetfilter )
+ {
+ G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) );
+ if ( strstr ( n1, name_match ) == NULL )
+ continue;
+ }
+ else
+ {
+ int mask = -1;
+ int dummy;
+ int scanflen = 0;
+ scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask );
+ if( scanflen != 5 || mask >= 32 )
+ {
+ continue;
+ }
+ }
+ }
+ else if( ip_match != NULL
+ && Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len ) != 0)
+ continue;
+
+ // only print out the the date part of made
+ date[ 0 ] = '\0';
+ made = g_admin_bans[ i ]->made;
+ for( j = 0; made && *made; j++ )
+ {
+ if( ( j + 1 ) >= sizeof( date ) )
+ break;
+ if( *made == ' ' )
+ break;
+ date[ j ] = *made;
+ date[ j + 1 ] = '\0';
+ made++;
+ }
+
+ if( g_admin_bans[ i ]->expires != 0
+ && ( g_admin_bans[ i ]->expires - t ) < 1 )
+ {
+ Com_sprintf( duration, sizeof( duration ), "^1*EXPIRED*^7" );
+ } else {
+ secs = ( g_admin_bans[ i ]->expires - t );
+ G_admin_duration( secs, duration, sizeof( duration ) );
+ }
+
+ suspended[ 0 ] = '\0';
+ if( g_admin_bans[ i ]->suspend > t )
+ {
+ G_admin_duration( g_admin_bans[ i ]->suspend - t, sduration, sizeof( sduration ) );
+ Com_sprintf( suspended, sizeof( suspended ), "^3*SUSPENDED*^7 for %s^7",
+ sduration );
+ }
+
+ G_DecolorString( g_admin_bans[ i ]->name, n1 );
+ Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is",
+ ( max_name + strlen( g_admin_bans[ i ]->name ) - strlen( n1 ) ) );
+ Com_sprintf( n1, sizeof( n1 ), name_fmt, g_admin_bans[ i ]->name );
+
+ G_DecolorString( g_admin_bans[ i ]->banner, n2 );
+ Com_sprintf( banner_fmt, sizeof( banner_fmt ), "%%%is",
+ ( max_banner + strlen( g_admin_bans[ i ]->banner ) - strlen( n2 ) ) );
+ Com_sprintf( n2, sizeof( n2 ), banner_fmt, g_admin_bans[ i ]->banner );
+ bannerslevel = g_admin_bans[ i ]->bannerlevel;
+
+ ADMBP( va( "%4i %s^7 %-15s %-8s %-10s\n | %-15s^7 Level:%2i\n | %s\n \\__ %s\n",
+ ( i + 1 ),
+ n1,
+ g_admin_bans[ i ]->ip,
+ date,
+ duration,
+ n2,
+ bannerslevel,
+ suspended,
+ g_admin_bans[ i ]->reason ) );
+
+ show_count++;
+ }
+
+ if (!numeric || ip_match)
+ {
+ char matchmethod[50];
+ if( numeric )
+ Com_sprintf( matchmethod, sizeof(matchmethod), "IP" );
+ else if( !subnetfilter )
+ Com_sprintf( matchmethod, sizeof(matchmethod), "name" );
+ else
+ Com_sprintf( matchmethod, sizeof(matchmethod), "ip range size" );
+
+
+ ADMBP( va( "^3!showbans:^7 found %d matching bans by %s. ",
+ show_count,
+ matchmethod ) );
+ }
+ else
+ {
+ ADMBP( va( "^3!showbans:^7 showing bans %d - %d of %d. ",
+ ( found ) ? ( start + 1 ) : 0,
+ ( ( start + MAX_ADMIN_SHOWBANS ) > found ) ?
+ found : ( start + MAX_ADMIN_SHOWBANS ),
+ found ) );
+ }
+
+ if( ( start + MAX_ADMIN_SHOWBANS ) < found )
+ {
+ ADMBP( va( "run !showbans %d %s to see more",
+ ( start + MAX_ADMIN_SHOWBANS + 1 ),
+ (filter[0]) ? filter : "" ) );
+ }
+ ADMBP( "\n" );
+ ADMBP_end();
+ return qtrue;
+}
+
+qboolean G_admin_help( gentity_t *ent, int skiparg )
+{
+ int i;
+ int commandsPerLine = 6;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ int j = 0;
+ int count = 0;
+
+ ADMBP_begin();
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) )
+ {
+ ADMBP( va( "^3!%-12s", g_admin_cmds[ i ].keyword ) );
+ j++;
+ count++;
+ }
+ // show 6 commands per line
+ if( j == 6 )
+ {
+ ADMBP( "\n" );
+ j = 0;
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ if( !G_admin_permission( ent, g_admin_commands[ i ]->flag ) )
+ continue;
+ ADMBP( va( "^3!%-12s", g_admin_commands[ i ]->command ) );
+ j++;
+ count++;
+ // show 6 commands per line
+ if( j == 6 )
+ {
+ ADMBP( "\n" );
+ j = 0;
+ }
+ }
+
+ if( count )
+ ADMBP( "\n" );
+ ADMBP( va( "^3!help: ^7%i available commands\n", count ) );
+ ADMBP( "run !help [^3command^7] for help with a specific command.\n" );
+ ADMBP( "The following non-standard /commands may also be available to you: \n" );
+ count = 1;
+
+ if( ent && g_AllStats.integer ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "allstats" ) );
+ count++;
+ }
+ if ( ent ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "builder" ) );
+ count++;
+ }
+ if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "callvote" ) );
+ count++;
+ }
+ if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "callteamvote" ) );
+ count++;
+ }
+ if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "donate" ) );
+ count++;
+ }
+ if( g_privateMessages.integer ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "m" ) );
+ count++;
+ }
+ if( ent && g_markDeconstruct.integer == 2 && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "mark" ) );
+ count++;
+ }
+ if( ent && g_actionPrefix.string[0] ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "me" ) );
+ count++;
+ }
+ if( ent && g_actionPrefix.string[0] ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "me_team" ) );
+ count++;
+ }
+ if( ent && g_actionPrefix.string[0] && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "mt" ) );
+ count++;
+ }
+ if( ent && g_myStats.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "mystats" ) );
+ count++;
+ }
+ if( ent && ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "protect" ) );
+ count++;
+ }
+ if( ent && ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "resign" ) );
+ count++;
+ }
+ if( g_publicSayadmins.integer || G_admin_permission( ent, ADMF_ADMINCHAT ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "say_admins" ) );
+ count++;
+ }
+ if( ent && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "say_area" ) );
+ count++;
+ }
+ if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "share" ) );
+ count++;
+ }
+ if( ent && g_teamStatus.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) {
+ if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" );
+ ADMBP( va( "^5/%-12s", "teamstatus" ) );
+ count++;
+ }
+ ADMBP( "\n" );
+ ADMBP_end();
+
+ return qtrue;
+ }
+ else
+ {
+ //!help param
+ char param[ MAX_ADMIN_CMD_LEN ];
+ char *cmd;
+
+ G_SayArgv( 1 + skiparg, param, sizeof( param ) );
+ cmd = ( param[0] == '!' ) ? &param[1] : &param[0];
+ ADMBP_begin();
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( !Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) )
+ {
+ if( !G_admin_permission( ent, g_admin_cmds[ i ].flag ) )
+ {
+ ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n",
+ g_admin_cmds[ i ].keyword ) );
+ ADMBP_end();
+ return qfalse;
+ }
+ ADMBP( va( "^3!help: ^7help for '!%s':\n",
+ g_admin_cmds[ i ].keyword ) );
+ ADMBP( va( " ^3Function: ^7%s\n", g_admin_cmds[ i ].function ) );
+ ADMBP( va( " ^3Syntax: ^7!%s %s\n", g_admin_cmds[ i ].keyword,
+ g_admin_cmds[ i ].syntax ) );
+ ADMBP( va( " ^3Flag: ^7'%s'\n", g_admin_cmds[ i ].flag ) );
+ ADMBP_end();
+ return qtrue;
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ if( !Q_stricmp( cmd, g_admin_commands[ i ]->command ) )
+ {
+ if( !G_admin_permission( ent, g_admin_commands[ i ]->flag ) )
+ {
+ ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n",
+ g_admin_commands[ i ]->command ) );
+ ADMBP_end();
+ return qfalse;
+ }
+ ADMBP( va( "^3!help: ^7help for '%s':\n",
+ g_admin_commands[ i ]->command ) );
+ ADMBP( va( " ^3Description: ^7%s\n", g_admin_commands[ i ]->desc ) );
+ ADMBP( va( " ^3Syntax: ^7!%s\n", g_admin_commands[ i ]->command ) );
+ ADMBP_end();
+ return qtrue;
+ }
+ }
+ ADMBP( va( "^3!help: ^7no help found for '%s'\n", cmd ) );
+ ADMBP_end();
+ return qfalse;
+ }
+}
+
+qboolean G_admin_admintest( gentity_t *ent, int skiparg )
+{
+ int i, l = 0;
+ qboolean found = qfalse;
+ qboolean lname = qfalse;
+
+ if( !ent )
+ {
+ ADMP( "^3!admintest: ^7you are on the console.\n" );
+ return qtrue;
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+
+ if( found )
+ {
+ l = g_admin_admins[ i ]->level;
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_levels[ i ]->level != l )
+ continue;
+ if( *g_admin_levels[ i ]->name )
+ {
+ lname = qtrue;
+ break;
+ }
+ }
+ }
+ AP( va( "print \"^3!admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"",
+ ent->client->pers.netname,
+ l,
+ ( lname ) ? "(" : "",
+ ( lname ) ? g_admin_levels[ i ]->name : "",
+ ( lname ) ? ")" : "" ) );
+ return qtrue;
+}
+
+qboolean G_admin_allready( gentity_t *ent, int skiparg )
+{
+ int i = 0;
+ gclient_t *cl;
+
+ if( !level.intermissiontime )
+ {
+ ADMP( "^3!allready: ^7this command is only valid during intermission\n" );
+ return qfalse;
+ }
+
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_NONE )
+ continue;
+
+ cl->readyToExit = 1;
+ }
+ AP( va( "print \"^3!allready:^7 %s^7 says everyone is READY now\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_cancelvote( gentity_t *ent, int skiparg )
+{
+
+ if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] )
+ {
+ ADMP( "^3!cancelvote^7: no vote in progress\n" );
+ return qfalse;
+ }
+ level.voteNo = level.numConnectedClients;
+ level.voteYes = 0;
+ CheckVote( );
+ level.teamVoteNo[ 0 ] = level.numConnectedClients;
+ level.teamVoteYes[ 0 ] = 0;
+ CheckTeamVote( PTE_HUMANS );
+ level.teamVoteNo[ 1 ] = level.numConnectedClients;
+ level.teamVoteYes[ 1 ] = 0;
+ CheckTeamVote( PTE_ALIENS );
+ AP( va( "print \"^3!cancelvote: ^7%s^7 decided that everyone voted No\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_passvote( gentity_t *ent, int skiparg )
+{
+ if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] )
+ {
+ ADMP( "^3!passvote^7: no vote in progress\n" );
+ return qfalse;
+ }
+ level.voteYes = level.numConnectedClients;
+ level.voteNo = 0;
+ CheckVote( );
+ level.teamVoteYes[ 0 ] = level.numConnectedClients;
+ level.teamVoteNo[ 0 ] = 0;
+ CheckTeamVote( PTE_HUMANS );
+ level.teamVoteYes[ 1 ] = level.numConnectedClients;
+ level.teamVoteNo[ 1 ] = 0;
+ CheckTeamVote( PTE_ALIENS );
+ AP( va( "print \"^3!passvote: ^7%s^7 decided that everyone voted Yes\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+static qboolean G_admin_global_pause( gentity_t *ent, int skiparg )
+{
+ if( level.paused )
+ {
+ level.pauseTime = level.time;
+ ADMP( "^3!pause: ^7Game is already paused, unpause timer reset\n" );
+ return qfalse;
+ }
+
+ level.paused = qtrue;
+ level.pauseTime = level.time;
+
+ level.pause_speed = g_speed.value;
+ level.pause_gravity = g_gravity.value;
+ level.pause_knockback = g_knockback.value;
+ level.pause_ff = g_friendlyFire.integer;
+ level.pause_ffb = g_friendlyBuildableFire.integer;
+
+ g_speed.value = 0;
+ g_gravity.value = 0;
+ g_knockback.value = 0;
+ g_friendlyFire.integer = 0;
+ g_friendlyBuildableFire.integer = 0;
+
+ CP( "cp \"^1Game is PAUSED\"" );
+ AP( va( "print \"^3!pause: ^7The game has been paused by %s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+
+ return qtrue;
+}
+
+static qboolean G_admin_global_unpause( gentity_t *ent, int skiparg )
+{
+ if( !level.paused )
+ {
+ ADMP( "^3!unpause: ^7Game is not paused\n" );
+ return qfalse;
+ }
+
+ level.paused = qfalse;
+
+ g_speed.value = level.pause_speed;
+ g_gravity.value = level.pause_gravity;
+ g_knockback.value = level.pause_knockback;
+ g_friendlyFire.integer = level.pause_ff;
+ g_friendlyBuildableFire.integer = level.pause_ffb;
+
+ CP( "cp \"^2Game is RESUMED\"" );
+ AP( va( "print \"^3!unpause: ^7The game has been resumed by %s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_pause( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ int i;
+ int count = 0;
+ gentity_t *vic;
+
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( cmd && *cmd == '!' )
+ cmd++;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ // global pause
+ if( !Q_stricmp( cmd, "pause" ) )
+ return G_admin_global_pause( ent, skiparg );
+ else
+ return G_admin_global_unpause( ent, skiparg );
+ }
+ if( G_SayArgc() != 2 + skiparg )
+ {
+ ADMP( va( "^3!%s: ^7usage: !%s (^5name|slot|*^7)\n", cmd, cmd ) );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( !Q_stricmp( name, "*" ) )
+ {
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ vic = &g_entities[ i ];
+ if( vic && vic->client &&
+ vic->client->pers.connected == CON_CONNECTED )
+ {
+ pids[ count ] = i;
+ count++;
+ }
+ }
+ }
+ else
+ {
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!%s: ^7%s\n", cmd, err ) );
+ return qfalse;
+ }
+ count = 1;
+ }
+
+ for( i = 0; i < count; i++ )
+ {
+ vic = &g_entities[ pids[ i ] ];
+ if ( !vic || !vic->client ) continue;
+ if( !admin_higher( ent, vic ) )
+ {
+ if( count == 1 )
+ ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", cmd ) );
+ continue;
+ }
+ if( vic->client->pers.paused )
+ {
+ if( !Q_stricmp( cmd, "pause" ) )
+ {
+ if( count == 1 )
+ ADMP( "^3!pause: ^7player is already paused\n" );
+ continue;
+ }
+ vic->client->pers.paused = qfalse;
+ CPx( pids[ i ], "cp \"^2You've been unpaused\"" );
+ if( count == 1 )
+ AP( va( "print \"^3!unpause: ^7%s^7 unpaused by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+ else
+ {
+ if( !Q_stricmp( cmd, "unpause" ) )
+ {
+ if( count == 1 )
+ ADMP( "^3!unpause: ^7player is already unpaused\n" );
+ continue;
+ }
+ if( count == 1 && ent == vic)
+ {
+ ADMP( "^3!pause: ^7you can not pause yourself\n" );
+ continue;
+ }
+ vic->client->pers.paused = qtrue;
+ CPx( pids[ i ], va( "cp \"^1You've been paused by ^7%s\"",
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ if( count == 1 )
+ AP( va( "print \"^3!pause: ^7%s^7 paused by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+ ClientUserinfoChanged( pids[ i ], qfalse );
+ }
+ return qtrue;
+}
+
+qboolean G_admin_spec999( gentity_t *ent, int skiparg )
+{
+ int i;
+ gentity_t *vic;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ vic = &g_entities[ i ];
+ if( !vic->client )
+ continue;
+ if( vic->client->pers.connected != CON_CONNECTED )
+ continue;
+ if( vic->client->pers.teamSelection == PTE_NONE )
+ continue;
+ if( vic->client->ps.ping == 999 )
+ {
+ G_ChangeTeam( vic, PTE_NONE );
+ AP( va( "print \"^3!spec999: ^7%s^7 moved ^7%s^7 to spectators\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ vic->client->pers.netname ) );
+ }
+ }
+ return qtrue;
+}
+
+qboolean G_admin_register(gentity_t *ent, int skiparg ){
+ int level = 0;
+
+ if( !ent ) return qtrue;
+
+ level = G_admin_level(ent);
+
+ if( level == 0 )
+ level = 1;
+
+ if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) )
+ {
+ ADMP( va( "^3!register: ^7 You cannot register for name protection until you update your client. Please replace your client executable with the one at http://trem.tjw.org/backport/ and reconnect. Updating your client will also allow you to have faster map downloads.\n" ) );
+ return qfalse;
+ }
+
+ if( g_newbieNumbering.integer
+ && g_newbieNamePrefix.string[ 0 ]
+ && !Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) )
+ {
+ ADMP( va( "^3!register: ^7 You cannot register names that begin with '%s^7'.\n",
+ g_newbieNamePrefix.string ) );
+ return qfalse;
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d %d;",ent - g_entities, level) );
+
+ AP( va( "print \"^3!register: ^7%s^7 is now a protected nickname.\n\"", ent->client->pers.netname) );
+
+ return qtrue;
+}
+
+qboolean G_admin_rename( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ];
+ char newname[ MAX_NAME_LENGTH ];
+ char oldname[ MAX_NAME_LENGTH ];
+ char err[ MAX_STRING_CHARS ];
+ char userinfo[ MAX_INFO_STRING ];
+ char *s;
+ gentity_t *victim = NULL;
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!rename: ^7usage: !rename [name] [newname]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ s = G_SayConcatArgs( 2 + skiparg );
+ Q_strncpyz( newname, s, sizeof( newname ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!rename: ^7%s\n", err ) );
+ return qfalse;
+ }
+ victim = &g_entities[ pids[ 0 ] ] ;
+ if( !admin_higher( ent, victim ) )
+ {
+ ADMP( "^3!rename: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ if( !G_admin_name_check( victim, newname, err, sizeof( err ) ) )
+ {
+ G_admin_print( ent, va( "^3!rename: Invalid name: ^7%s\n", err ) );
+ return qfalse;
+ }
+ level.clients[ pids[ 0 ] ].pers.nameChanges--;
+ level.clients[ pids[ 0 ] ].pers.nameChangeTime = 0;
+ trap_GetUserinfo( pids[ 0 ], userinfo, sizeof( userinfo ) );
+ s = Info_ValueForKey( userinfo, "name" );
+ Q_strncpyz( oldname, s, sizeof( oldname ) );
+ Info_SetValueForKey( userinfo, "name", newname );
+ trap_SetUserinfo( pids[ 0 ], userinfo );
+ ClientUserinfoChanged( pids[ 0 ], qtrue );
+ if( strcmp( oldname, level.clients[ pids[ 0 ] ].pers.netname ) )
+ AP( va( "print \"^3!rename: ^7%s^7 has been renamed to %s^7 by %s\n\"",
+ oldname,
+ level.clients[ pids[ 0 ] ].pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_restart( gentity_t *ent, int skiparg )
+{
+ char layout[ MAX_CVAR_VALUE_STRING ] = { "" };
+ char teampref[ MAX_CVAR_VALUE_STRING ] = { "" };
+ int i;
+ gclient_t *cl;
+
+ if( G_SayArgc( ) > 1 + skiparg )
+ {
+ char map[ MAX_QPATH ];
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ G_SayArgv( skiparg + 1, layout, sizeof( layout ) );
+
+ if( Q_stricmp( layout, "keepteams" ) && Q_stricmp( layout, "keepteamslock" ) && Q_stricmp( layout, "switchteams" ) && Q_stricmp( layout, "switchteamslock" ) )
+ {
+ if( !Q_stricmp( layout, "*BUILTIN*" ) ||
+ trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
+ NULL, FS_READ ) > 0 )
+ {
+ trap_Cvar_Set( "g_layouts", layout );
+ }
+ else
+ {
+ ADMP( va( "^3!restart: ^7layout '%s' does not exist\n", layout ) );
+ return qfalse;
+ }
+ }
+ else
+ {
+ strcpy(layout,"");
+ G_SayArgv( skiparg + 1, teampref, sizeof( teampref ) );
+ }
+ }
+
+ if( G_SayArgc( ) > 2 + skiparg )
+ {
+ G_SayArgv( skiparg + 2, teampref, sizeof( teampref ) );
+ }
+
+
+ if( !Q_stricmp( teampref, "keepteams" ) || !Q_stricmp( teampref, "keepteamslock" ) )
+ {
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_NONE )
+ continue;
+
+ cl->sess.restartTeam = cl->pers.teamSelection;
+ }
+ }
+ else if(!Q_stricmp( teampref, "switchteams" ) || !Q_stricmp( teampref, "switchteamslock" ))
+ {
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_NONE )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_ALIENS )
+ cl->sess.restartTeam = PTE_HUMANS;
+ else if(cl->pers.teamSelection == PTE_HUMANS )
+ cl->sess.restartTeam = PTE_ALIENS;
+ }
+ }
+
+ if( !Q_stricmp( teampref, "switchteamslock" ) || !Q_stricmp( teampref, "keepteamslock" ) )
+ {
+ trap_Cvar_Set( "g_lockTeamsAtStart", "1" );
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, "map_restart" );
+
+ if(teampref[ 0 ])
+ strcpy(teampref,va( "^7(with teams option: '%s^7')", teampref ));
+
+ G_admin_maplog_result( "R" );
+
+ AP( va( "print \"^3!restart: ^7map restarted by %s %s %s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "",
+ teampref ) );
+ return qtrue;
+}
+
+qboolean G_admin_nobuild( gentity_t *ent, int skiparg )
+{
+ char buffer[ MAX_STRING_CHARS ];
+ int i, tmp;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!nobuild: ^7usage: !nobuild (^5enable / disable / log / remove / save^7)\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, buffer, sizeof( buffer ) );
+
+ if( !Q_stricmp( buffer, "enable" ) || !Q_stricmp( buffer, "Enable" ) )
+ {
+ if( G_SayArgc() < 4 + skiparg )
+ {
+ ADMP( "^3!nobuild: ^7usage: !nobuild enable (^5area^7) (^5height^7)\n" );
+ return qfalse;
+ }
+
+ if( !level.noBuilding )
+ {
+
+ level.noBuilding = qtrue;
+
+ // Grab and set the area
+ G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) );
+ tmp = atoi( buffer );
+ level.nbArea = tmp;
+
+ // Grab and set the height
+ G_SayArgv( 3 + skiparg, buffer, sizeof( buffer ) );
+ tmp = atoi( buffer );
+ level.nbHeight = tmp;
+
+ ADMP( "^3!nobuild: ^7nobuild is now enabled, please place a buildable to spawn a marker\n" );
+ return qtrue;
+ }
+ else
+ {
+ ADMP( "^3!nobuild: ^7nobuild is already enabled. type !nobuild disable to disable nobuild mode.\n" );
+ return qfalse;
+ }
+ }
+
+ if( !Q_stricmp( buffer, "disable" ) || !Q_stricmp( buffer, "Disable" ) )
+ {
+ if( level.noBuilding )
+ {
+ level.noBuilding = qfalse;
+ level.nbArea = 0.0f;
+ level.nbHeight = 0.0f;
+ ADMP( "^3!nobuild: ^7nobuild is now disabled\n" );
+ return qtrue;
+ }
+ else
+ {
+ ADMP( "^3!nobuild: ^7nobuild is disabled. type !nobuild enable (^5area^7) (^5height^7) to enable nobuild mode.\n" );
+ return qfalse;
+ }
+ }
+
+ if( !Q_stricmp( buffer, "log" ) || !Q_stricmp( buffer, "Log" ) )
+ {
+ ADMBP_begin();
+
+ tmp = 0;
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ if( level.nbMarkers[ i ].Marker == NULL )
+ continue;
+
+ // This will display a start at 1, and not 0. This is to stop confusion by server ops
+ ADMBP( va( "^7#%i at origin (^5%f %f %f^7)^7\n", i + 1, level.nbMarkers[ i ].Origin[0], level.nbMarkers[ i ].Origin[1], level.nbMarkers[ i ].Origin[2] ) );
+ tmp++;
+ }
+ ADMBP( va( "^3!nobuild:^7 displaying %i marker(s)\n", tmp ) );
+
+ ADMBP_end();
+ return qtrue;
+ }
+
+ if( !Q_stricmp( buffer, "remove" ) || !Q_stricmp( buffer, "Remove" ) )
+ {
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!nobuild: ^7usage: !nobuild remove (^5marker #^7)\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) );
+ tmp = atoi( buffer ) - 1;
+ // ^ that was to work with the display number...
+
+ if( level.nbMarkers[ tmp ].Marker == NULL )
+ {
+ ADMP( "^3!nobuild: ^7that is not a valid marker number\n" );
+ return qfalse;
+ }
+ // Donno why im doing this, but lets clear this...
+ level.nbMarkers[ tmp ].Marker->noBuild.isNB = qfalse;
+ level.nbMarkers[ tmp ].Marker->noBuild.Area = 0.0f;
+ level.nbMarkers[ tmp ].Marker->noBuild.Height = 0.0f;
+
+ // Free the entitiy and null it out...
+ G_FreeEntity( level.nbMarkers[ tmp ].Marker );
+ level.nbMarkers[ tmp ].Marker = NULL;
+
+ // That is to work with the display number...
+ ADMP( va( "^3!nobuild:^7 marker %i has been removed\n", tmp + 1 ) );
+ return qtrue;
+ }
+
+ if( !Q_stricmp( buffer, "save" ) || !Q_stricmp( buffer, "Save" ) )
+ {
+ int i, tmp;
+
+ G_NobuildSave( );
+
+ tmp = 0;
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ if( level.nbMarkers[ i ].Marker == NULL )
+ continue;
+
+ tmp++;
+ }
+ ADMP( va( "^3!nobuild:^7 %i nobuild markers have been saved\n", tmp ) );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean G_admin_nextmap( gentity_t *ent, int skiparg )
+{
+ AP( va( "print \"^3!nextmap: ^7%s^7 decided to load the next map\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ level.lastWin = PTE_NONE;
+ trap_SetConfigstring( CS_WINNER, "Evacuation" );
+ LogExit( va( "nextmap was run by %s",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ G_admin_maplog_result( "N" );
+ return qtrue;
+}
+
+qboolean G_admin_namelog( gentity_t *ent, int skiparg )
+{
+ int i, j;
+ char search[ MAX_NAME_LENGTH ] = {""};
+ char s2[ MAX_NAME_LENGTH ] = {""};
+ char n2[ MAX_NAME_LENGTH ] = {""};
+ char guid_stub[ 9 ];
+ qboolean found = qfalse;
+ int printed = 0;
+
+ if( G_SayArgc() > 1 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, search, sizeof( search ) );
+ G_SanitiseString( search, s2, sizeof( s2 ) );
+ }
+ ADMBP_begin();
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ if( search[0] )
+ {
+ found = qfalse;
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
+ g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+ if( !found )
+ continue;
+ }
+ printed++;
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+ if( g_admin_namelog[ i ]->slot > -1 )
+ ADMBP( "^3" );
+ ADMBP( va( "%-2s (*%s) %15s^7",
+ (g_admin_namelog[ i ]->slot > -1 ) ?
+ va( "%d", g_admin_namelog[ i ]->slot ) : "-",
+ guid_stub, g_admin_namelog[ i ]->ip ) );
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
+ g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ ADMBP( va( " '%s^7'", g_admin_namelog[ i ]->name[ j ] ) );
+ }
+ ADMBP( "\n" );
+ }
+ ADMBP( va( "^3!namelog:^7 %d recent clients found\n", printed ) );
+ ADMBP_end();
+ return qtrue;
+}
+
+qboolean G_admin_lock( gentity_t *ent, int skiparg )
+{
+ char teamName[2] = {""};
+ pTeam_t team;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!lock: ^7usage: !lock [a|h]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) );
+ if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' )
+ team = PTE_ALIENS;
+ else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' )
+ team = PTE_HUMANS;
+ else
+ {
+ ADMP( va( "^3!lock: ^7invalid team\"%c\"\n", teamName[0] ) );
+ return qfalse;
+ }
+
+ if( team == PTE_ALIENS )
+ {
+ if( level.alienTeamLocked )
+ {
+ ADMP( "^3!lock: ^7Alien team is already locked\n" );
+ return qfalse;
+ }
+ else
+ level.alienTeamLocked = qtrue;
+ }
+ else if( team == PTE_HUMANS ) {
+ if( level.humanTeamLocked )
+ {
+ ADMP( "^3!lock: ^7Human team is already locked\n" );
+ return qfalse;
+ }
+ else
+ level.humanTeamLocked = qtrue;
+ }
+
+ AP( va( "print \"^3!lock: ^7%s team has been locked by %s\n\"",
+ ( team == PTE_ALIENS ) ? "Alien" : "Human",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_unlock( gentity_t *ent, int skiparg )
+{
+ char teamName[2] = {""};
+ pTeam_t team;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!unlock: ^7usage: !unlock [a|h]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) );
+ if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' )
+ team = PTE_ALIENS;
+ else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' )
+ team = PTE_HUMANS;
+ else
+ {
+ ADMP( va( "^3!unlock: ^7invalid team\"%c\"\n", teamName[0] ) );
+ return qfalse;
+ }
+
+ if( team == PTE_ALIENS )
+ {
+ if( !level.alienTeamLocked )
+ {
+ ADMP( "^3!unlock: ^7Alien team is not currently locked\n" );
+ return qfalse;
+ }
+ else
+ level.alienTeamLocked = qfalse;
+ }
+ else if( team == PTE_HUMANS ) {
+ if( !level.humanTeamLocked )
+ {
+ ADMP( "^3!unlock: ^7Human team is not currently locked\n" );
+ return qfalse;
+ }
+ else
+ level.humanTeamLocked = qfalse;
+ }
+
+ AP( va( "print \"^3!unlock: ^7%s team has been unlocked by %s\n\"",
+ ( team == PTE_ALIENS ) ? "Alien" : "Human",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_designate( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ gentity_t *vic;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!designate: ^7usage: designate [name|slot#]\n" );
+ return qfalse;
+ }
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( cmd && *cmd == '!' )
+ cmd++;
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!designate: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) &&
+ !Q_stricmp( cmd, "undesignate" ) )
+ {
+ ADMP( "^3!designate: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ if( vic->client->pers.designatedBuilder == qtrue )
+ {
+ if( !Q_stricmp( cmd, "designate" ) )
+ {
+ ADMP( "^3!designate: ^7player is already designated builder\n" );
+ return qtrue;
+ }
+ vic->client->pers.designatedBuilder = qfalse;
+ CPx( pids[ 0 ], "cp \"^1Your designation has been revoked\"" );
+ AP( va(
+ "print \"^3!designate: ^7%s^7's designation has been revoked by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ G_CheckDBProtection( );
+ }
+ else
+ {
+ if( !Q_stricmp( cmd, "undesignate" ) )
+ {
+ ADMP( "^3!undesignate: ^7player is not currently designated builder\n" );
+ return qtrue;
+ }
+ vic->client->pers.designatedBuilder = qtrue;
+ CPx( pids[ 0 ], "cp \"^1You've been designated\"" );
+ AP( va( "print \"^3!designate: ^7%s^7 has been designated by ^7%s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ return qtrue;
+}
+
+ //!Warn by Gate (Daniel Evans)
+qboolean G_admin_warn( gentity_t *ent, int skiparg )
+{//mostly copy and paste with the proper lines altered from !mute and !kick
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
+ int minargc;
+ gentity_t *vic;
+
+ minargc = 3 + skiparg;
+ if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!warn: ^7usage: warn [name] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ reason = G_SayConcatArgs( 2 + skiparg );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!warn: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( "^3!warn: ^7sorry, but your intended victim has a higher admin"
+ " level than you.\n" );
+ return qfalse;
+ }
+
+ vic = &g_entities[ pids[ 0 ] ];
+ //next line is the onscreen warning
+ CPx( pids[ 0 ],va("cp \"^1You have been warned by an administrator.\n ^3Cease immediately or face admin action!\n^1 %s%s\"",(*reason)? "REASON: " : "" ,(*reason)? reason : "") );
+ AP( va( "print \"^3!warn: ^7%s^7 has been warned to cease and desist: %s by %s \n\"",
+ vic->client->pers.netname, (*reason) ? reason : "his current activity",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );//console announcement
+ return qtrue;
+}
+
+qboolean G_admin_putmespec( gentity_t *ent, int skiparg )
+{
+ if( !ent )
+ {
+ ADMP( "!specme: sorry, but console isn't allowed on the spectators team\n");
+ return qfalse;
+ }
+
+ if( level.paused )
+ {
+ ADMP("!specme: disabled when game is paused\n");
+ return qfalse;
+ }
+
+ if(ent->client->pers.teamSelection == PTE_NONE)
+ return qfalse;
+
+ //guard against build timer exploit
+ if( ent->client->pers.teamSelection != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
+ ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ||
+ ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ||
+ BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) ||
+ BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ ADMP("!specme: You cannot leave your team until the build timer expires");
+ return qfalse;
+ }
+
+ G_ChangeTeam( ent, PTE_NONE );
+ AP( va("print \"^3!specme: ^7%s^7 decided to join the spectators\n\"", ent->client->pers.netname ) );
+ return qtrue;
+}
+
+qboolean G_admin_slap( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ gentity_t *vic;
+ vec3_t dir;
+
+ if( level.intermissiontime ) return qfalse;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!slap: ^7usage: !slap [name|slot#]\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!slap: ^7%s\n", err ) );
+ return qfalse;
+ }
+
+ vic = &g_entities[ pids[ 0 ] ];
+ if( !vic )
+ {
+ ADMP( "^3!slap: ^7bad target\n" );
+ return qfalse;
+ }
+ if( vic == ent )
+ {
+ ADMP( "^3!slap: ^7sorry, you cannot slap yourself\n" );
+ return qfalse;
+ }
+ if( !admin_higher( ent, vic ) )
+ {
+ ADMP( "^3!slap: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ if( vic->client->pers.teamSelection == PTE_NONE ||
+ vic->client->pers.classSelection == PCL_NONE )
+ {
+ ADMP( "^3!slap: ^7can't slap spectators\n" );
+ return qfalse;
+ }
+
+ // knockback in a random direction
+ dir[0] = crandom();
+ dir[1] = crandom();
+ dir[2] = random();
+ G_Knockback( vic, dir, g_slapKnockback.integer );
+
+ trap_SendServerCommand( vic-g_entities,
+ va( "cp \"%s^7 is not amused\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+
+ if( g_slapDamage.integer > 0 )
+ {
+ int damage;
+
+ if( G_SayArgc() > 2 + skiparg )
+ {
+ char dmg_str[ MAX_STRING_CHARS ];
+ G_SayArgv( 2 + skiparg, dmg_str, sizeof( dmg_str ) );
+ damage = atoi(dmg_str);
+ if( damage < 0 ) damage = 0;
+ }
+ else
+ {
+ if( g_slapDamage.integer > 100 ) g_slapDamage.integer = 100;
+ damage = BG_FindHealthForClass( vic->client->ps.stats[ STAT_PCLASS ] ) *
+ g_slapDamage.integer / 100;
+ if( damage < 1 ) damage = 1;
+ }
+
+ vic->health -= damage;
+ vic->client->ps.stats[ STAT_HEALTH ] = vic->health;
+ vic->lastDamageTime = level.time;
+ if( vic->health <= 0 )
+ {
+ vic->flags |= FL_NO_KNOCKBACK;
+ vic->enemy = &g_entities[ pids[ 0 ] ];
+ vic->die( vic, ent, ent, damage, MOD_SLAP );
+ }
+ else if( vic->pain )
+ {
+ vic->pain( vic, &g_entities[ pids[ 0 ] ], damage );
+ }
+ }
+ return qtrue;
+}
+
+qboolean G_admin_drop( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!drop: ^7usage: !drop [name|slot#] [message]\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!drop: ^7%s\n", err ) );
+ return qfalse;
+ }
+
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( "^3!drop: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+
+ // victim's message
+ if( G_SayArgc() > 2 + skiparg )
+ trap_SendServerCommand( pids[ 0 ],
+ va( "disconnect \"You have been dropped.\n%s^7\n\"",
+ G_SayConcatArgs( 2 + skiparg ) ) );
+ else
+ trap_SendServerCommand( pids[ 0 ], va( "disconnect" ) );
+
+ // server message
+ trap_DropClient( pids[ 0 ], va( "disconnected" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_buildlog( gentity_t *ent, int skiparg )
+{
+#define LOG_DISPLAY_LENGTH 10
+ buildHistory_t *ptr;
+ gentity_t *builder = NULL;
+ int skip = 0, start = 0, lastID = -1, firstID = -1, i, len, matchlen = 0;
+ pTeam_t team = PTE_NONE;
+ char message[ MAX_STRING_CHARS ], *teamchar;
+ char *name, *action, *buildablename, markstring[ MAX_STRING_CHARS ];
+ if( !g_buildLogMaxLength.integer )
+ {
+ ADMP( "^3!buildlog: ^7build logging is disabled" );
+ return qfalse;
+ }
+ if( G_SayArgc( ) >= 2 + skiparg )
+ {
+ for( i = 1; i + skiparg < G_SayArgc( ); i++ )
+ {
+ char argbuf[ 64 ], err[ MAX_STRING_CHARS ];
+ int x = 0, pids[ MAX_CLIENTS ];
+ G_SayArgv( i + skiparg, argbuf, sizeof argbuf );
+ switch( argbuf[ 0 ])
+ {
+ case 'x':
+ x = 1;
+ default:
+ skip = atoi( argbuf + x );
+ start = 0;
+ break;
+ case '#':
+ start = atoi( argbuf + 1 );
+ skip = 0;
+ break;
+ case '-':
+ if(G_ClientNumbersFromString(argbuf + 1, pids) != 1)
+ {
+ G_MatchOnePlayer(pids, err, sizeof(err));
+ ADMP(va("^3!revert: ^7%s\n", err));
+ return qfalse;
+ }
+ builder = g_entities + *pids;
+ break;
+ case 'A':
+ case 'a':
+ team = PTE_ALIENS;
+ break;
+ case 'H':
+ case 'h':
+ team = PTE_HUMANS;
+ break;
+ }
+ }
+ }
+ // !buildlog can be abused, so let everyone know when it is used
+ AP( va( "print \"^3!buildlog: ^7%s^7 requested a log of recent building"
+ " activity\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ len = G_CountBuildLog( ); // also clips the log if too long
+ if( !len )
+ {
+ ADMP( "^3!buildlog: ^7no build log found\n" );
+ return qfalse;
+ }
+ if( start )
+ {
+ // set skip based on start
+ for( ptr = level.buildHistory; ptr && ptr->ID != start;
+ ptr = ptr->next, skip++ );
+ if( !ptr )
+ {
+ ADMP( "^3!buildlog: ^7log ID not found\n" );
+ skip = 0;
+ }
+ }
+ // ensure skip is a useful value
+ if( skip > len - LOG_DISPLAY_LENGTH )
+ skip = len - LOG_DISPLAY_LENGTH;
+ *message = '\0';
+ // skip to start entry
+ for( ptr = level.buildHistory, i = len; ptr && i > len - skip;
+ ptr = ptr->next )
+ {
+ // these checks could perhaps be done more efficiently but they are cheap
+ // in processor time so I'm not worrying
+ if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) )
+ continue;
+ if( builder && builder != ptr->ent )
+ continue;
+ matchlen++;
+ i--;
+ }
+ for( ; i + LOG_DISPLAY_LENGTH > len - skip && i > 0; i--, ptr = ptr->next )
+ {
+ if( !ptr )
+ break; // run out of log
+ *markstring = '\0'; // reinit markstring
+ // check team
+ if( ( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) )
+ || ( builder && builder != ptr->ent ) )
+ {
+ skip++; // loop an extra time because we skipped one
+ continue;
+ }
+ if( lastID < 0 )
+ lastID = ptr->ID;
+ firstID = ptr->ID;
+ matchlen++;
+ // set name to the ent's current name or last recorded name
+ if( ptr->ent )
+ {
+ if( ptr->ent->client )
+ name = ptr->ent->client->pers.netname;
+ else
+ name = "<world>"; // any non-client action
+ }
+ else
+ name = ptr->name;
+ switch( ptr->fate )
+ {
+ case BF_BUILT:
+ action = "^2built^7 a";
+ break;
+ case BF_DECONNED:
+ action = "^3DECONSTRUCTED^7 a";
+ break;
+ case BF_DESTROYED:
+ action = "destroyed a";
+ break;
+ case BF_TEAMKILLED:
+ action = "^1TEAMKILLED^7 a";
+ break;
+ default:
+ action = "\0"; // erm
+ break;
+ }
+ // handle buildables removed by markdecon
+ if( ptr->marked )
+ {
+ buildHistory_t *mark;
+ int j, markdecon[ BA_NUM_BUILDABLES ], and = 2;
+ char bnames[ 32 ], *article;
+ mark = ptr;
+ // count the number of buildables
+ memset( markdecon, 0, sizeof( markdecon ) );
+ while( ( mark = mark->marked ) )
+ markdecon[ mark->buildable ]++;
+ // reverse order makes grammar easier
+ for( j = BA_NUM_BUILDABLES; j >= 0; j-- )
+ {
+ buildablename = BG_FindHumanNameForBuildable( j );
+ // plural is easy
+ if( markdecon[ j ] > 1 )
+ Com_sprintf( bnames, 32, "%d %ss", markdecon[ j ], buildablename );
+ // use an appropriate article
+ else if( markdecon[ j ] == 1 )
+ {
+ if( BG_FindUniqueTestForBuildable( j ) )
+ article = "the"; // if only one
+ else if( strchr( "aeiouAEIOU", *buildablename ) )
+ article = "an"; // if first char is vowel
+ else
+ article = "a";
+ Com_sprintf( bnames, 32, "%s %s", article, buildablename );
+ }
+ else
+ continue; // none of this buildable
+ // C grammar: x, y, and z
+ // the integer and is 2 initially, the test means it is used on the
+ // second sprintf only, the reverse order makes this second to last
+ // the comma is only printed if there is already some markstring i.e.
+ // not the first time ( which would put it on the end of the string )
+ Com_sprintf( markstring, sizeof( markstring ), "%s%s %s%s", bnames,
+ ( *markstring ) ? "," : "", ( and-- == 1 ) ? "and " : "", markstring );
+ }
+ }
+ buildablename = BG_FindHumanNameForBuildable( ptr->buildable );
+ switch( BG_FindTeamForBuildable( ptr->buildable ) )
+ {
+ case PTE_ALIENS:
+ teamchar = "^1A";
+ break;
+ case PTE_HUMANS:
+ teamchar = "^4H";
+ break;
+ default:
+ teamchar = " "; // space so it lines up neatly
+ break;
+ }
+ // prepend the information to the string as we go back in buildhistory
+ // so the earliest events are at the top
+ Com_sprintf( message, MAX_STRING_CHARS, "%3d %s^7 %s^7 %s%s %s%s%s\n%s",
+ ptr->ID, teamchar, name, action,
+ ( strchr( "aeiouAEIOU", buildablename[ 0 ] ) ) ? "n" : "",
+ buildablename, ( markstring[ 0 ] ) ? ", removing " : "",
+ markstring, message );
+ }
+ for( ; ptr; ptr = ptr->next )
+ {
+ if( builder && builder != ptr->ent )
+ continue;
+ if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) )
+ continue;
+ matchlen++;
+ }
+ if( matchlen )
+ ADMP( va( "%s^3!buildlog: showing log entries %d - %d of %d\n", message,
+ firstID, lastID, matchlen ) );
+ else
+ ADMP( "^3!buildlog: ^7no log entries match those criteria\n" );
+ return qtrue;
+}
+
+qboolean G_admin_revert( gentity_t *ent, int skiparg )
+{
+ int i = 0, j = 0, repeat = 1, ID = 0, len, matchlen=0;
+ pTeam_t team = PTE_NONE;
+ qboolean force = qfalse, reached = qfalse;
+ gentity_t *builder = NULL, *targ;
+ buildHistory_t *ptr, *tmp, *mark, *prev;
+ vec3_t dist;
+ char argbuf[ 64 ], *name, *bname, *action, *article;
+ len = G_CountBuildLog( );
+ if( !len )
+ {
+ ADMP( "^3!revert: ^7no build log found\n" );
+ return qfalse;
+ }
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" );
+ return qfalse;
+ }
+ for( i = 1; i + skiparg < G_SayArgc( ); i++ )
+ {
+ char arg[ 64 ], err[ MAX_STRING_CHARS ];
+ int pids[ MAX_CLIENTS ];
+ G_SayArgv( i + skiparg, arg, sizeof arg );
+ switch( arg[ 0 ])
+ {
+ case 'x':
+ repeat = atoi( arg + 1 );
+ break;
+ case '#':
+ ID = atoi( arg + 1 );
+ break;
+ case '-':
+ if(G_ClientNumbersFromString(arg + 1, pids) != 1)
+ {
+ G_MatchOnePlayer(pids, err, sizeof err);
+ ADMP(va("^3!revert: ^7%s\n", err));
+ return qfalse;
+ }
+ builder = g_entities + *pids;
+ break;
+ case 'A':
+ case 'a':
+ team = PTE_ALIENS;
+ break;
+ case 'H':
+ case 'h':
+ team = PTE_HUMANS;
+ break;
+ case '!':
+ force = qtrue;
+ break;
+ default:
+ ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" );
+ return qfalse;
+ }
+ }
+ if( repeat > 25 )
+ {
+ ADMP( "^3!revert: ^7to avoid flooding, can only revert 25 builds at a time\n" );
+ repeat = 25;
+ }
+ for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 )
+ {
+ if( !ptr )
+ break; // run out of bhist
+ if( !reached && ID )
+ {
+ if( ptr->ID == ID )
+ reached = qtrue;
+ else
+ {
+ prev = ptr;
+ ptr = ptr->next;
+ repeat++;
+ continue;
+ }
+ }
+ if( ( team != PTE_NONE &&
+ team != BG_FindTeamForBuildable( ptr->buildable ) ) ||
+ ( builder && builder != ptr->ent ))
+ {
+ // team doesn't match, so skip this ptr and reset prev
+ prev = ptr;
+ ptr = ptr->next;
+ // we don't want to count this one so counteract the decrement by the for
+ repeat++;
+ continue;
+ }
+ // get the ent's current or last recorded name
+ if( ptr->ent )
+ {
+ if( ptr->ent->client )
+ name = ptr->ent->client->pers.netname;
+ else
+ name = "<world>"; // non-client actions
+ }
+ else
+ name = ptr->name;
+ bname = BG_FindHumanNameForBuildable( ptr->buildable );
+ action = "";
+ switch( ptr->fate )
+ {
+ case BF_BUILT:
+ action = "^2build^7";
+ for( j = MAX_CLIENTS, targ = g_entities + j;
+ j < level.num_entities; j++, targ++ )
+ {
+ // easy checks first to save time
+ if( targ->s.eType != ET_BUILDABLE )
+ continue;
+ if( targ->s.modelindex != ptr->buildable )
+ continue;
+ VectorSubtract( targ->s.pos.trBase, ptr->origin, dist );
+#define FIND_BUILDABLE_TOLERANCE 5
+ if( VectorLength( dist ) > FIND_BUILDABLE_TOLERANCE )
+ continue; // number is somewhat arbitrary, watch for false pos/neg
+ // if we didn't continue then it's this one, unlink it but we can't
+ // free it yet, because the markdecon buildables might not place
+ trap_UnlinkEntity( targ );
+ break;
+ }
+ // if there are marked buildables to replace, and we aren't overriding
+ // space check, check they can fit before acting
+ if( ptr->marked && !force )
+ {
+ for( mark = ptr->marked; mark; mark = mark->marked )
+ if( !G_RevertCanFit( mark ) )
+ {
+ trap_LinkEntity( targ ); // put it back, we failed
+ // scariest sprintf ever:
+ Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!",
+ ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "",
+ ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "",
+ ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "",
+ ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" );
+ ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would conflict with "
+ "another buildable, use ^3!revert %s ^7to override\n", action, argbuf ) );
+ return qfalse;
+ }
+ }
+ // Prevent teleport glitch when reverting an occupied hovel
+ if( targ->s.modelindex == BA_A_HOVEL &&
+ targ->active )
+ {
+ gentity_t *builder = targ->builder;
+ vec3_t newOrigin;
+ vec3_t newAngles;
+
+ VectorCopy( targ->s.angles, newAngles );
+ newAngles[ ROLL ] = 0;
+
+ VectorCopy( targ->s.origin, newOrigin );
+ VectorMA( newOrigin, 1.0f, targ->s.origin2, newOrigin );
+
+ //prevent lerping
+ builder->client->ps.eFlags ^= EF_TELEPORT_BIT;
+ builder->client->ps.eFlags &= ~EF_NODRAW;
+ G_UnlaggedClear( builder );
+
+ G_SetOrigin( builder, newOrigin );
+ VectorCopy( newOrigin, builder->client->ps.origin );
+ G_SetClientViewAngle( builder, newAngles );
+
+ //client leaves hovel
+ builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
+ }
+
+ // if we haven't returned yet then we're good to go, free it
+ G_FreeEntity( targ );
+ // put the marked buildables back and mark them again
+ if( ptr->marked ) // there may be a more efficient way of doing this
+ {
+ for( mark = ptr->marked; mark; mark = mark->marked )
+ G_SpawnRevertedBuildable( mark, qtrue );
+ }
+ break;
+ case BF_DECONNED:
+ if( !action[ 0 ] ) action = "^3deconstruction^7";
+ case BF_TEAMKILLED:
+ if( !action[ 0 ] ) action ="^1TEAMKILL^7";
+ case BF_DESTROYED:
+ if( !action[ 0 ] ) action = "destruction";
+ // if we're not overriding and the replacement can't fit, as before
+ if( !force && !G_RevertCanFit( ptr ) )
+ {
+ Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!",
+ ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "",
+ ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "",
+ ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "",
+ ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" );
+ ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would "
+ "conflict with another buildable, use ^3!revert %s ^7to override\n",
+ action, argbuf ) );
+ return qfalse;
+ }
+ // else replace it but don't mark it ( it might have been marked before
+ // but it isn't that important )
+ G_SpawnRevertedBuildable( ptr, qfalse );
+ break;
+ default:
+ // if this happens something has gone wrong
+ ADMP( "^3!revert: ^7incomplete or corrupted build log entry\n" );
+ /* quarantine and dispose of the log, it's dangerous
+ trap_Cvar_Set( "g_buildLogMaxLength", "0" );
+ G_CountBuildLog( );
+ */
+ return qfalse;
+ }
+ if( j == level.num_entities )
+ {
+ ADMP( va( "^3!revert: ^7could not find logged buildable #%d\n", ptr->ID ));
+ prev = ptr;
+ ptr = ptr->next;
+ continue;
+ }
+ // this is similar to the buildlog stuff
+ if( BG_FindUniqueTestForBuildable( ptr->buildable ) )
+ article = "the";
+ else if( strchr( "aeiouAEIOU", *bname ) )
+ article = "an";
+ else
+ article = "a";
+ AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ name, strchr( "Ss", name[ strlen( name ) - 1 ] ) ? "" : "s",
+ action, article, bname ) );
+ matchlen++;
+ // remove the reverted entry
+ // ptr moves on, prev just readjusts ->next unless it is about to be
+ // freed, in which case it is forced to move on too
+ tmp = ptr;
+ if( ptr == level.buildHistory )
+ prev = level.buildHistory = ptr = ptr->next;
+ else
+ prev->next = ptr = ptr->next;
+ G_Free( tmp );
+ }
+ if( ID && !reached )
+ {
+ ADMP( "^3!revert: ^7no buildlog entry with that ID\n" );
+ return qfalse;
+ }
+
+ if( !matchlen )
+ {
+ ADMP( "^3!revert: ^7no log entries match those criteria\n" );
+ return qfalse;
+ }
+ else
+ {
+ ADMP( va( "^3!revert: ^7reverted %d buildlog events\n", matchlen ) );
+ }
+
+ return qtrue;
+}
+
+void G_Unescape( char *input, char *output, int len );
+qboolean G_StringReplaceCvars( char *input, char *output, int len );
+
+qboolean G_admin_info( gentity_t *ent, int skiparg )
+{
+ fileHandle_t infoFile;
+ int length;
+ char filename[ MAX_OSPATH ], message[ MAX_STRING_CHARS ];
+ if( G_SayArgc() == 2 + skiparg )
+ G_SayArgv( 1 + skiparg, filename, sizeof( filename ) );
+ else if( G_SayArgc() == 1 + skiparg )
+ Q_strncpyz( filename, "default", sizeof( filename ) );
+ else
+ {
+ ADMP( "^3!info: ^7usage: ^3!info ^7(^5subject^7)\n" );
+ return qfalse;
+ }
+ Com_sprintf( filename, sizeof( filename ), "info/info-%s.txt", filename );
+ length = trap_FS_FOpenFile( filename, &infoFile, FS_READ );
+ if( length <= 0 || !infoFile )
+ {
+ trap_FS_FCloseFile( infoFile );
+ ADMP( "^3!info: ^7no relevant information is available\n" );
+ return qfalse;
+ }
+ else
+ {
+ int i;
+ char *cr;
+ trap_FS_Read( message, sizeof( message ), infoFile );
+ if( length < sizeof( message ) )
+ message[ length ] = '\0';
+ else
+ message[ sizeof( message ) - 1 ] = '\0';
+ trap_FS_FCloseFile( infoFile );
+ // strip carriage returns for windows platforms
+ while( ( cr = strchr( message, '\r' ) ) )
+ memmove( cr, cr + 1, strlen( cr + 1 ) + 1 );
+#define MAX_INFO_PARSE_LOOPS 100
+ for( i = 0; i < MAX_INFO_PARSE_LOOPS &&
+ G_StringReplaceCvars( message, message, sizeof( message ) ); i++ );
+ G_Unescape( message, message, sizeof( message ) );
+ if( i == MAX_INFO_PARSE_LOOPS )
+ G_Printf( S_COLOR_YELLOW "WARNING: %s exceeds MAX_INFO_PARSE_LOOPS\n", filename );
+ ADMP( va( "%s\n", message ) );
+ return qtrue;
+ }
+}
+
+void G_Unescape( char *input, char *output, int len )
+{
+ // \n -> newline, \%c -> %c
+ // output is terminated at output[len - 1]
+ // it's OK for input to equal output, because our position in input is always
+ // equal or greater than our position in output
+ // however, if output is later in the same string as input, a crash is pretty
+ // much inevitable
+ int i, j;
+ for( i = j = 0; input[i] && j + 1 < len; i++, j++ )
+ {
+ if( input[i] == '\\' )
+ {
+ if( !input[++i] )
+ {
+ output[j] = '\0';
+ return;
+ }
+ else if( input[i] == 'n' )
+ output[j] = '\n';
+ else
+ output[j] = input[i];
+ }
+ else
+ output[j] = input[i];
+ }
+ output[j] = '\0';
+}
+
+qboolean G_StringReplaceCvars( char *input, char *output, int len )
+{
+ int i, outNum = 0;
+ char cvarName[ 64 ], cvarValue[ MAX_CVAR_VALUE_STRING ];
+ char *outputBuffer;
+ qboolean doneAnything = qfalse;
+ if( len <= 0 )
+ return qfalse;
+ // use our own internal buffer in case output == input
+ outputBuffer = G_Alloc( len );
+ len -= 1; // fit in a terminator
+ while( *input && outNum < len )
+ {
+ if( *input == '\\' && input[1] && outNum < len - 1 )
+ {
+ outputBuffer[ outNum++ ] = *input++;
+ outputBuffer[ outNum++ ] = *input++;
+ }
+ else if( *input == '$' )
+ {
+ doneAnything = qtrue;
+ input++;
+ if( *input == '{' )
+ input++;
+ for( i = 0; *input && ( isalnum( *input ) || *input == '_' ) &&
+ i < 63; i++ )
+ cvarName[ i ] = *input++;
+ cvarName[ i ] = '\0';
+ if( *input == '}' )
+ input++;
+ trap_Cvar_VariableStringBuffer( cvarName, cvarValue, sizeof( cvarValue ) );
+ if( cvarValue[ 0 ] )
+ {
+ for( i = 0; cvarValue[ i ] && outNum < len; i++ )
+ outputBuffer[ outNum++ ] = cvarValue[ i ];
+ }
+ }
+ else
+ outputBuffer[ outNum++ ] = *input++;
+ }
+ outputBuffer[ outNum ] = '\0';
+ Q_strncpyz( output, outputBuffer, len );
+ G_Free( outputBuffer );
+ return doneAnything;
+}
+
+/*
+================
+ G_admin_print
+
+ This function facilitates the ADMP define. ADMP() is similar to CP except
+ that it prints the message to the server console if ent is not defined.
+================
+*/
+void G_admin_print( gentity_t *ent, char *m )
+{
+ if( ent )
+ trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) );
+ else
+ {
+ char m2[ MAX_STRING_CHARS ];
+ if( !trap_Cvar_VariableIntegerValue( "com_ansiColor" ) )
+ {
+ G_DecolorString( m, m2 );
+ G_Printf( m2 );
+ }
+ else
+ G_Printf( m );
+ }
+}
+
+void G_admin_buffer_begin()
+{
+ g_bfb[ 0 ] = '\0';
+}
+
+void G_admin_buffer_end( gentity_t *ent )
+{
+ ADMP( g_bfb );
+}
+
+void G_admin_buffer_print( gentity_t *ent, char *m )
+{
+ // 1022 - strlen("print 64 \"\"") - 1
+ if( strlen( m ) + strlen( g_bfb ) >= 1009 )
+ {
+ ADMP( g_bfb );
+ g_bfb[ 0 ] = '\0';
+ }
+ Q_strcat( g_bfb, sizeof( g_bfb ), m );
+}
+
+
+void G_admin_cleanup()
+{
+ int i = 0;
+
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ G_Free( g_admin_levels[ i ] );
+ g_admin_levels[ i ] = NULL;
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ G_Free( g_admin_admins[ i ] );
+ g_admin_admins[ i ] = NULL;
+ }
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ G_Free( g_admin_bans[ i ] );
+ g_admin_bans[ i ] = NULL;
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ G_Free( g_admin_commands[ i ] );
+ g_admin_commands[ i ] = NULL;
+ }
+}
+
+qboolean G_admin_L0(gentity_t *ent, int skiparg ){
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ] = {""};
+ char testname[ MAX_NAME_LENGTH ] = {""};
+ char err[ MAX_STRING_CHARS ];
+ qboolean numeric = qtrue;
+ int i;
+ int id = -1;
+ gentity_t *vic;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!L0: ^7usage: !L0 [name|slot#|admin#]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, testname, sizeof( testname ) );
+ G_SanitiseString( testname, name, sizeof( name ) );
+ for( i = 0; i < sizeof( name ) && name[ i ] ; i++ )
+ {
+ if( name[ i ] < '0' || name[ i ] > '9' )
+ {
+ numeric = qfalse;
+ break;
+ }
+ }
+
+ if( numeric )
+ {
+ id = atoi( name );
+ }
+ else
+ {
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!L0: ^7%s\n", err ) );
+ return qfalse;
+ }
+ id = pids[ 0 ];
+ }
+
+ if (id >= 0 && id < level.maxclients)
+ {
+ vic = &g_entities[ id ];
+ if( !vic || !(vic->client) || vic->client->pers.connected != CON_CONNECTED )
+ {
+ ADMP( "^3!L0:^7 no one connected by that slot number\n" );
+ return qfalse;
+ }
+
+ if( G_admin_level( vic ) != 1 )
+ {
+ ADMP( "^3!L0:^7 intended victim is not level 1\n" );
+ return qfalse;
+ }
+ }
+ else if (id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS
+ && g_admin_admins[ id - MAX_CLIENTS ] )
+ {
+ if( g_admin_admins[ id - MAX_CLIENTS ]->level != 1 )
+ {
+ ADMP( "^3!L0:^7 intended victim is not level 1\n" );
+ return qfalse;
+ }
+ }
+ else
+ {
+ ADMP( "^3!L0:^7 no match. use !listplayers or !listadmins to "
+ "find an appropriate number to use instead of name.\n" );
+ return qfalse;
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "!setlevel %d 0;", id ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_L1(gentity_t *ent, int skiparg ){
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
+ int minargc;
+
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!L1: ^7usage: !L1 [name]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ reason = G_SayConcatArgs( 2 + skiparg );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!L1: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( G_admin_level(&g_entities[ pids[ 0 ] ] )>0 )
+ {
+ ADMP( "^3!L1: ^7Sorry, but that person is already higher than level 0.\n" );
+ return qfalse;
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d 1;", pids[ 0 ] ) );
+ return qtrue;
+}
+
+qboolean G_admin_invisible( gentity_t *ent, int skiparg )
+{
+ if( !ent )
+ {
+ ADMP( "!invisible: console can not become invisible.\n" );
+ return qfalse;
+ }
+
+ if ( ent->client->sess.invisible != qtrue )
+ {
+ // Make the player invisible
+ G_ChangeTeam( ent, PTE_NONE );
+ ent->client->sess.invisible = qtrue;
+ ClientUserinfoChanged( ent->client->pers.connection->clientNum, qfalse );
+ G_admin_namelog_update( ent->client, qtrue );
+ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " disconnected\n\"", ent->client->pers.netname ) );
+ }
+ else
+ {
+ // Make the player visible
+ ent->client->sess.invisible = qfalse;
+ ClientUserinfoChanged( ent->client->pers.connection->clientNum, qfalse );
+ G_admin_namelog_update( ent->client, qfalse );
+ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", ent->client->pers.netname ) );
+ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", ent->client->pers.netname ) );
+ }
+ return qtrue;
+}
+
+qboolean G_admin_decon( gentity_t *ent, int skiparg )
+{
+ int i = 0, j = 0, repeat = 24, pids[ MAX_CLIENTS ], len, matchlen = 0;
+ pTeam_t team = PTE_NONE;
+ qboolean force = qfalse, reached = qfalse;
+ gentity_t *builder = NULL, *targ;
+ buildHistory_t *ptr, *tmp, *mark, *prev;
+ vec3_t dist;
+ char arg[ 64 ], err[ MAX_STRING_CHARS ], *name, *bname, *action, *article, *reason;
+ len = G_CountBuildLog( );
+
+ if( !len )
+ {
+ ADMP( "^3!decon: ^7no build log found, aborting...\n" );
+ return qfalse;
+ }
+
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!decon: ^7usage: !decon (^5name|num^7)\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, arg, sizeof( arg ) );
+ if( G_ClientNumbersFromString( arg, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!decon: ^7%s\n", err ) );
+ return qfalse;
+ }
+
+ builder = g_entities + *pids;
+ if( builder->client->sess.invisible == qtrue )
+ {
+ ADMP( va( "^3!decon: ^7no connected player by the name or slot #\n" ) );
+ return qfalse;
+ }
+
+ if( !admin_higher( ent, builder ) )
+ {
+ ADMP( "^3!decon: ^7sorry, but your intended victim has a higher admin"
+ "level than you\n");
+ return qfalse;
+ }
+
+ for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 )
+ {
+ if( !ptr )
+ break;
+ if( builder && builder != ptr->ent )
+ {
+ // team doesn't match, so skip this ptr and reset prev
+ prev = ptr;
+ ptr = ptr->next;
+ // we don't want to count this one so counteract the decrement by the for
+ repeat++;
+ continue;
+ }
+ // get the ent's current or last recorded name
+ if( ptr->ent )
+ {
+ if( ptr->ent->client )
+ name = ptr->ent->client->pers.netname;
+ else
+ name = "<world>"; // non-client actions
+ }
+ else
+ name = ptr->name;
+ bname = BG_FindHumanNameForBuildable( ptr->buildable );
+ action = "";
+ switch( ptr->fate )
+ {
+ case BF_BUILT:
+ prev = ptr;
+ ptr = ptr->next;
+ repeat++;
+ continue;
+ case BF_DESTROYED:
+ prev = ptr;
+ ptr = ptr->next;
+ repeat++;
+ case BF_DECONNED:
+ if( !action[0] ) action = "^3deconstruction^7";
+ case BF_TEAMKILLED:
+ if( !action[0] ) action = "^1TEAMKILL^7";
+ // if we're not overriding and the replacement can't fit, as before
+ if( !G_RevertCanFit( ptr ) )
+ {
+ prev = ptr;
+ ptr = ptr->next;
+ repeat++;
+ continue;
+ }
+ // else replace it but don't mark it ( it might have been marked before
+ // but it isn't that important )
+ G_SpawnRevertedBuildable( ptr, qfalse );
+ break;
+ default:
+ // if this happens something has gone wrong
+ ADMP( "^3!decon: ^7incomplete or corrupted build log entry\n" );
+ /* quarantine and dispose of the log, it's dangerous
+ trap_Cvar_Set( "g_buildLogMaxLength", "0" );
+ G_CountBuildLog( );
+ */
+ return qfalse;
+ }
+ // this is similar to the buildlog stuff
+ if( BG_FindUniqueTestForBuildable( ptr->buildable ) )
+ article = "the";
+ else if( strchr( "aeiouAEIOU", *bname ) )
+ article = "an";
+ else
+ article = "a";
+ AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ name, strchr( "Ss", name[ strlen( name ) - 1 ] ) ? "" : "s",
+ action, article, bname ) );
+ matchlen++;
+ // remove the reverted entry
+ // ptr moves on, prev just readjusts ->next unles it is about to be
+ // freed, in which case it is forced to move on too
+ tmp = ptr;
+ if( ptr == level.buildHistory )
+ prev = level.buildHistory = ptr = ptr->next;
+ else
+ prev->next = ptr = ptr->next;
+ G_Free( tmp );
+ }
+
+ if( !matchlen )
+ {
+ ADMP( "^3!decopn: ^7This user doesn't seem to have deconned anything...\n" );
+ return qfalse;
+ }
+
+ ADMP( va( "^3!decon: ^7reverted %d buildlog events\n", matchlen ) );
+ admin_create_ban( ent,
+ builder->client->pers.netname,
+ builder->client->pers.guid,
+ builder->client->pers.ip, G_admin_parse_time( g_deconBanTime.string ),
+ ( *reason ) ? reason : "^1Decon" );
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+
+ trap_SendServerCommand( pids[ 0 ],
+ va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"",
+ ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole",
+ ( *reason ) ? reason : "^1Decon" ) );
+
+ trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s",
+ ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console",
+ ( *reason ) ? reason : "^1Decon" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_setdevmode( gentity_t *ent, int skiparg )
+{
+ char str[ 5 ];
+
+ if( G_SayArgc() != 2 + skiparg )
+ {
+ ADMP( "^3!setdevmode: ^7usage: !setdevmode [on|off]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, str, sizeof( str ) );
+
+ if( !Q_stricmp( str, "on" ) )
+ {
+ if( g_cheats.integer )
+ {
+ ADMP( "^3!setdevmode: ^7developer mode is already on\n" );
+ return qfalse;
+ }
+ trap_Cvar_Set( "sv_cheats", "1" );
+ trap_Cvar_Update( &g_cheats );
+ AP( va( "print \"^3!setdevmode: ^7%s ^7has switched developer mode on\n\"",
+ ent ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ else if( !Q_stricmp( str, "off" ) )
+ {
+ if( !g_cheats.integer )
+ {
+ ADMP( "^3!setdevmode: ^7developer mode is already off\n" );
+ return qfalse;
+ }
+ trap_Cvar_Set( "sv_cheats", "0" );
+ trap_Cvar_Update( &g_cheats );
+ AP( va( "print \"^3!setdevmode: ^7%s ^7has switched developer mode off\n\"",
+ ent ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ else
+ {
+ ADMP( "^3!setdevmode: ^7usage: !setdevmode [on|off]\n" );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+qboolean G_admin_hstage( gentity_t *ent, int skiparg )
+{
+
+ char lvl_chr[ MAX_STRING_CHARS ];
+ int minargc;
+ int lvl;
+
+
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!hstage: ^7hstage: !hstage [#]\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, lvl_chr, sizeof( lvl_chr ) );
+
+ lvl = atoi(lvl_chr);
+
+ lvl -= 1;
+ trap_SendConsoleCommand( EXEC_APPEND, va( "g_humanStage %i", lvl ) );
+
+ return qtrue;
+
+}
+
+qboolean G_admin_astage( gentity_t *ent, int skiparg )
+{
+
+ char lvl_chr[ MAX_STRING_CHARS ];
+ int minargc;
+ int lvl;
+
+
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!astage: ^7astage: !astage [#]\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, lvl_chr, sizeof( lvl_chr ) );
+
+ lvl = atoi(lvl_chr);
+
+ lvl -= 1;
+ trap_SendConsoleCommand( EXEC_APPEND, va( "g_alienStage %i", lvl ) );
+
+ return qtrue;
+
+}
diff --git a/src/game/g_admin.h b/src/game/g_admin.h
new file mode 100644
index 0000000..ed5f99f
--- /dev/null
+++ b/src/game/g_admin.h
@@ -0,0 +1,276 @@
+/*
+===========================================================================
+Copyright (C) 2004-2006 Tony J. White
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef _G_ADMIN_H
+#define _G_ADMIN_H
+
+#define AP(x) trap_SendServerCommand(-1, x)
+#define CP(x) trap_SendServerCommand(ent-g_entities, x)
+#define CPx(x, y) trap_SendServerCommand(x, y)
+#define ADMP(x) G_admin_print(ent, x)
+#define ADMBP(x) G_admin_buffer_print(ent, x)
+#define ADMBP_begin() G_admin_buffer_begin()
+#define ADMBP_end() G_admin_buffer_end(ent)
+
+#define MAX_ADMIN_LEVELS 32
+#define MAX_ADMIN_ADMINS 1024
+#define MAX_ADMIN_BANS 1024
+#define MAX_ADMIN_NAMELOGS 128
+#define MAX_ADMIN_NAMELOG_NAMES 5
+#define MAX_ADMIN_ADMINLOGS 128
+#define MAX_ADMIN_ADMINLOG_ARGS 50
+#define MAX_ADMIN_FLAG_LEN 20
+#define MAX_ADMIN_FLAGS 1024
+#define MAX_ADMIN_COMMANDS 64
+#define MAX_ADMIN_CMD_LEN 20
+#define MAX_ADMIN_BAN_REASON 50
+#define MAX_ADMIN_BANSUSPEND_DAYS 14
+
+/*
+ * IMMUNITY - cannot be vote kicked, vote muted
+ * NOCENSORFLOOD - cannot be censored or flood protected
+ * TEAMCHANGEFREE - never loses credits for changing teams
+ * SPECALLCHAT - can see team chat as a spectator
+ * FORCETEAMCHANGE - can switch teams any time, regardless of balance
+ * UNACCOUNTABLE - does not need to specify a reason for a kick/ban
+ * NOVOTELIMIT - can call a vote at any time (regardless of a vote being
+ * disabled or voting limitations)
+ * CANPERMBAN - does not need to specify a duration for a ban
+ * TEAMCHATCMD - can run commands from team chat
+ * ACTIVITY - inactivity rules do not apply to them
+ * IMMUTABLE - admin commands cannot be used on them
+ * INCOGNITO - does not show up as an admin in !listplayers
+ * ADMINCHAT - receives and can send /a admin messages
+ * SEESFULLLISTPLAYERS - sees all information in !listplayers
+ * DBUILDER - permanent designated builder
+ * STEALTH - uses admin stealth
+ * SPECIAL - allows some special permissions (unlimited votes etc)
+ * SPECIALNAME - allows black text in name
+ * .NOCHAT - mutes a player on connect
+ * .NOVOTE - disallows voting by a player
+ * ALLFLAGS - all flags (including command flags) apply to this player
+ */
+
+
+#define ADMF_IMMUNITY "IMMUNITY"
+#define ADMF_NOCENSORFLOOD "NOCENSORFLOOD"
+#define ADMF_TEAMCHANGEFREE "TEAMCHANGEFREE"
+#define ADMF_SPEC_ALLCHAT "SPECALLCHAT"
+#define ADMF_FORCETEAMCHANGE "FORCETEAMCHANGE"
+#define ADMF_UNACCOUNTABLE "UNACCOUNTABLE"
+#define ADMF_NO_VOTE_LIMIT "NOVOTELIMIT"
+#define ADMF_CAN_PERM_BAN "CANPERMBAN"
+#define ADMF_TEAMCHAT_CMD "TEAMCHATCMD"
+#define ADMF_ACTIVITY "ACTIVITY"
+
+#define ADMF_IMMUTABLE "IMMUTABLE"
+#define ADMF_INCOGNITO "INCOGNITO"
+#define ADMF_ADMINCHAT "ADMINCHAT"
+#define ADMF_HIGHADMINCHAT "HIGHADMINCHAT"
+#define ADMF_SEESFULLLISTPLAYERS "SEESFULLLISTPLAYERS"
+#define ADMF_DBUILDER "DBUILDER"
+#define ADMF_ADMINSTEALTH "STEALTH"
+#define ADMF_ALLFLAGS "ALLFLAGS"
+
+#define ADMF_BAN_IMMUNITY "BANIMMUNITY"
+
+#define ADMF_SPECIAL "SPECIAL"
+#define ADMF_SPECIALNAME "SPECIALNAME"
+
+#define ADMF_NO_BUILD ".NOBUILD"
+#define ADMF_NO_CHAT ".NOCHAT"
+#define ADMF_NO_VOTE ".NOVOTE"
+
+#define MAX_ADMIN_LISTITEMS 20
+#define MAX_ADMIN_SHOWBANS 10
+#define MAX_ADMIN_MAPLOG_LENGTH 5
+
+// important note: QVM does not seem to allow a single char to be a
+// member of a struct at init time. flag has been converted to char*
+typedef struct
+{
+ char *keyword;
+ qboolean ( * handler ) ( gentity_t *ent, int skiparg );
+ char *flag;
+ char *function; // used for !help
+ char *syntax; // used for !help
+}
+g_admin_cmd_t;
+
+typedef struct g_admin_level
+{
+ int level;
+ char name[ MAX_NAME_LENGTH ];
+ char flags[ MAX_ADMIN_FLAGS ];
+}
+g_admin_level_t;
+
+typedef struct g_admin_admin
+{
+ char guid[ 33 ];
+ char name[ MAX_NAME_LENGTH ];
+ int level;
+ char flags[ MAX_ADMIN_FLAGS ];
+ int seen;
+}
+g_admin_admin_t;
+
+typedef struct g_admin_ban
+{
+ char name[ MAX_NAME_LENGTH ];
+ char guid[ 33 ];
+ char ip[ 20 ];
+ char reason[ MAX_ADMIN_BAN_REASON ];
+ char made[ 18 ]; // big enough for strftime() %c
+ int expires;
+ int suspend;
+ char banner[ MAX_NAME_LENGTH ];
+ int bannerlevel;
+}
+g_admin_ban_t;
+
+typedef struct g_admin_command
+{
+ char command[ MAX_ADMIN_CMD_LEN ];
+ char exec[ MAX_QPATH ];
+ char desc[ 50 ];
+ char flag[ MAX_ADMIN_FLAG_LEN ];
+}
+g_admin_command_t;
+
+typedef struct g_admin_namelog
+{
+ char name[ MAX_ADMIN_NAMELOG_NAMES ][MAX_NAME_LENGTH ];
+ char ip[ 16 ];
+ char guid[ 33 ];
+ int slot;
+ qboolean banned;
+ qboolean muted;
+ int muteExpires;
+ qboolean denyBuild;
+ int denyHumanWeapons;
+ int denyAlienClasses;
+ int specExpires;
+ int voteCount;
+}
+g_admin_namelog_t;
+
+typedef struct g_admin_adminlog
+{
+ char name[ MAX_NAME_LENGTH ];
+ char command[ MAX_ADMIN_CMD_LEN ];
+ char args[ MAX_ADMIN_ADMINLOG_ARGS ];
+ int id;
+ int time;
+ int level;
+ qboolean success;
+}
+g_admin_adminlog_t;
+
+qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen );
+qboolean G_admin_cmd_check( gentity_t *ent, qboolean say );
+qboolean G_admin_readconfig( gentity_t *ent, int skiparg );
+qboolean G_admin_permission( gentity_t *ent, const char *flag );
+qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len );
+void G_admin_namelog_update( gclient_t *ent, qboolean disconnect );
+void G_admin_maplog_result( char *flag );
+int G_admin_level( gentity_t *ent );
+void G_admin_set_adminname( gentity_t *ent );
+char* G_admin_adminPrintName( gentity_t *ent );
+
+qboolean G_admin_seen(gentity_t *ent, int skiparg );
+void G_admin_seen_update( char *guid );
+
+// ! command functions
+qboolean G_admin_time( gentity_t *ent, int skiparg );
+qboolean G_admin_setlevel( gentity_t *ent, int skiparg );
+qboolean G_admin_flaglist( gentity_t *ent, int skiparg );
+qboolean G_admin_flag( gentity_t *ent, int skiparg );
+qboolean G_admin_kick( gentity_t *ent, int skiparg );
+qboolean G_admin_adjustban( gentity_t *ent, int skiparg );
+qboolean G_admin_subnetban( gentity_t *ent, int skiparg );
+qboolean G_admin_suspendban( gentity_t *ent, int skiparg );
+qboolean G_admin_ban( gentity_t *ent, int skiparg );
+qboolean G_admin_unban( gentity_t *ent, int skiparg );
+qboolean G_admin_putteam( gentity_t *ent, int skiparg );
+qboolean G_admin_adminlog( gentity_t *ent, int skiparg );
+void G_admin_adminlog_cleanup( void );
+void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skiparg, qboolean success );
+qboolean G_admin_listadmins( gentity_t *ent, int skiparg );
+qboolean G_admin_listlayouts( gentity_t *ent, int skiparg );
+qboolean G_admin_listplayers( gentity_t *ent, int skiparg );
+qboolean G_admin_listmaps( gentity_t *ent, int skiparg );
+qboolean G_admin_listrotation( gentity_t *ent, int skiparg );
+qboolean G_admin_map( gentity_t *ent, int skiparg );
+qboolean G_admin_devmap( gentity_t *ent, int skiparg );
+void G_admin_maplog_update( void );
+qboolean G_admin_maplog( gentity_t *ent, int skiparg );
+qboolean G_admin_layoutsave( gentity_t *ent, int skiparg );
+qboolean G_admin_demo( gentity_t *ent, int skiparg );
+qboolean G_admin_mute( gentity_t *ent, int skiparg );
+qboolean G_admin_denybuild( gentity_t *ent, int skiparg );
+qboolean G_admin_denyweapon( gentity_t *ent, int skiparg );
+qboolean G_admin_showbans( gentity_t *ent, int skiparg );
+qboolean G_admin_help( gentity_t *ent, int skiparg );
+qboolean G_admin_admintest( gentity_t *ent, int skiparg );
+qboolean G_admin_allready( gentity_t *ent, int skiparg );
+qboolean G_admin_cancelvote( gentity_t *ent, int skiparg );
+qboolean G_admin_passvote( gentity_t *ent, int skiparg );
+qboolean G_admin_spec999( gentity_t *ent, int skiparg );
+qboolean G_admin_register( gentity_t *ent, int skiparg );
+qboolean G_admin_rename( gentity_t *ent, int skiparg );
+qboolean G_admin_restart( gentity_t *ent, int skiparg );
+qboolean G_admin_nobuild( gentity_t *ent, int skiparg );
+qboolean G_admin_nextmap( gentity_t *ent, int skiparg );
+qboolean G_admin_namelog( gentity_t *ent, int skiparg );
+qboolean G_admin_lock( gentity_t *ent, int skiparg );
+qboolean G_admin_unlock( gentity_t *ent, int skiparg );
+qboolean G_admin_info( gentity_t *ent, int skiparg );
+qboolean G_admin_buildlog( gentity_t *ent, int skiparg );
+qboolean G_admin_revert( gentity_t *ent, int skiparg );
+qboolean G_admin_decon( gentity_t *ent, int skiparg );
+qboolean G_admin_pause( gentity_t *ent, int skiparg );
+qboolean G_admin_L0( gentity_t *ent, int skiparg );
+qboolean G_admin_L1( gentity_t *ent, int skiparg );
+qboolean G_admin_putmespec( gentity_t *ent, int skiparg );
+qboolean G_admin_warn( gentity_t *ent, int skiparg );
+qboolean G_admin_designate( gentity_t *ent, int skiparg );
+qboolean G_admin_cp( gentity_t *ent, int skiparg );
+
+qboolean G_admin_slap( gentity_t *ent, int skiparg );
+qboolean G_admin_drop( gentity_t *ent, int skiparg );
+qboolean G_admin_invisible( gentity_t *ent, int skiparg );
+qboolean G_admin_setdevmode( gentity_t *ent, int skiparg );
+qboolean G_admin_hstage( gentity_t *ent, int skiparg );
+qboolean G_admin_astage( gentity_t *ent, int skiparg );
+
+void G_admin_print( gentity_t *ent, char *m );
+void G_admin_buffer_print( gentity_t *ent, char *m );
+void G_admin_buffer_begin( void );
+void G_admin_buffer_end( gentity_t *ent );
+
+void G_admin_duration( int secs, char *duration, int dursize );
+void G_admin_cleanup( void );
+void G_admin_namelog_cleanup( void );
+void admin_writeconfig( void );
+
+#endif /* ifndef _G_ADMIN_H */
diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c
new file mode 100644
index 0000000..e1cdc32
--- /dev/null
+++ b/src/game/g_buildable.c
@@ -0,0 +1,4709 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+// from g_combat.c
+extern char *modNames[ ];
+
+/*
+================
+G_SetBuildableAnim
+
+Triggers an animation client side
+================
+*/
+void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force )
+{
+ int localAnim = anim;
+
+ if( force )
+ localAnim |= ANIM_FORCEBIT;
+
+ // don't toggle the togglebit more than once per frame
+ if( ent->animTime != level.time )
+ {
+ localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT );
+ ent->animTime = level.time;
+ }
+ else
+ localAnim |= ent->s.legsAnim & ANIM_TOGGLEBIT;
+
+ ent->s.legsAnim = localAnim;
+}
+
+/*
+================
+G_SetIdleBuildableAnim
+
+Set the animation to use whilst no other animations are running
+================
+*/
+void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim )
+{
+ ent->s.torsoAnim = anim;
+}
+
+/*
+===============
+G_CheckSpawnPoint
+
+Check if a spawn at a specified point is valid
+===============
+*/
+gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal,
+ buildable_t spawn, vec3_t spawnOrigin )
+{
+ float displacement;
+ vec3_t mins, maxs;
+ vec3_t cmins, cmaxs;
+ vec3_t localOrigin;
+ trace_t tr;
+
+ BG_FindBBoxForBuildable( spawn, mins, maxs );
+
+ if( spawn == BA_A_SPAWN )
+ {
+ VectorSet( cmins, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX );
+ VectorSet( cmaxs, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX );
+
+ displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3;
+ VectorMA( origin, displacement, normal, localOrigin );
+
+ trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT );
+
+ if( tr.entityNum != ENTITYNUM_NONE )
+ return &g_entities[ tr.entityNum ];
+
+ trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID );
+
+ if( tr.entityNum == ENTITYNUM_NONE )
+ {
+ if( spawnOrigin != NULL )
+ VectorCopy( localOrigin, spawnOrigin );
+
+ return NULL;
+ }
+ else
+ return &g_entities[ tr.entityNum ];
+ }
+ else if( spawn == BA_H_SPAWN )
+ {
+ BG_FindBBoxForClass( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL );
+
+ VectorCopy( origin, localOrigin );
+ localOrigin[ 2 ] += maxs[ 2 ] + fabs( cmins[ 2 ] ) + 1.0f;
+
+ trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT );
+
+ if( tr.entityNum != ENTITYNUM_NONE )
+ return &g_entities[ tr.entityNum ];
+
+ trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID );
+
+ if( tr.entityNum == ENTITYNUM_NONE )
+ {
+ if( spawnOrigin != NULL )
+ VectorCopy( localOrigin, spawnOrigin );
+
+ return NULL;
+ }
+ else
+ return &g_entities[ tr.entityNum ];
+ }
+
+ return NULL;
+}
+
+/*
+================
+G_NumberOfDependants
+
+Return number of entities that depend on this one
+================
+*/
+static int G_NumberOfDependants( gentity_t *self )
+{
+ int i, n = 0;
+ gentity_t *ent;
+
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent->parentNode == self )
+ n++;
+ }
+
+ return n;
+}
+
+#define POWER_REFRESH_TIME 2000
+
+/*
+================
+G_FindPower
+
+attempt to find power for self, return qtrue if successful
+================
+*/
+static qboolean G_FindPower( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ gentity_t *closestPower = NULL;
+ int distance = 0;
+ int minDistance = 10000;
+ vec3_t temp_v;
+
+ if( self->biteam != BIT_HUMANS )
+ return qfalse;
+
+ //reactor is always powered
+ if( self->s.modelindex == BA_H_REACTOR )
+ return qtrue;
+
+ //if this already has power then stop now
+ if( self->parentNode && self->parentNode->powered )
+ return qtrue;
+
+ //reset parent
+ self->parentNode = NULL;
+
+ //iterate through entities
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ //if entity is a power item calculate the distance to it
+ if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) &&
+ ent->spawned )
+ {
+ VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ distance = VectorLength( temp_v );
+
+ if( distance < minDistance && ent->powered &&
+ ( ( ent->s.modelindex == BA_H_REACTOR &&
+ distance <= REACTOR_BASESIZE ) ||
+ ( ent->s.modelindex == BA_H_REPEATER &&
+ distance <= REPEATER_BASESIZE ) ) ) {
+
+ closestPower = ent;
+ minDistance = distance;
+ }
+ }
+ }
+
+ //if there were no power items nearby give up
+ if( closestPower ) {
+ self->parentNode = closestPower;
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+/*
+================
+G_IsPowered
+
+Simple wrapper to G_FindPower to check if a location has power
+================
+*/
+qboolean G_IsPowered( vec3_t origin )
+{
+ gentity_t dummy;
+
+ dummy.parentNode = NULL;
+ dummy.biteam = BIT_HUMANS;
+ dummy.s.modelindex = BA_NONE;
+ VectorCopy( origin, dummy.s.origin );
+
+ return G_FindPower( &dummy );
+}
+
+/*
+================
+G_FindDCC
+
+attempt to find a controlling DCC for self, return qtrue if successful
+================
+*/
+static qboolean G_FindDCC( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ gentity_t *closestDCC = NULL;
+ int distance = 0;
+ int minDistance = 10000;
+ vec3_t temp_v;
+ qboolean foundDCC = qfalse;
+
+ if( self->biteam != BIT_HUMANS )
+ return qfalse;
+
+ //if this already has dcc then stop now
+ if( self->dccNode && self->dccNode->powered )
+ return qtrue;
+
+ //reset parent
+ self->dccNode = NULL;
+
+ //iterate through entities
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ //if entity is a dcc calculate the distance to it
+ if( ent->s.modelindex == BA_H_DCC && ent->spawned )
+ {
+ VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ distance = VectorLength( temp_v );
+ if( ( !foundDCC || distance < minDistance ) && ent->powered )
+ {
+ closestDCC = ent;
+ minDistance = distance;
+ foundDCC = qtrue;
+ }
+ }
+ }
+
+ //if there was no nearby DCC give up
+ if( !foundDCC )
+ return qfalse;
+
+ self->dccNode = closestDCC;
+
+ return qtrue;
+}
+
+/*
+================
+G_IsDCCBuilt
+
+simple wrapper to G_FindDCC to check for a dcc
+================
+*/
+qboolean G_IsDCCBuilt( void )
+{
+ gentity_t dummy;
+
+ memset( &dummy, 0, sizeof( gentity_t ) );
+
+ dummy.dccNode = NULL;
+ dummy.biteam = BIT_HUMANS;
+
+ return G_FindDCC( &dummy );
+}
+
+/*
+================
+G_FindOvermind
+
+Attempt to find an overmind for self
+================
+*/
+static qboolean G_FindOvermind( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+
+ if( self->biteam != BIT_ALIENS )
+ return qfalse;
+
+ //if this already has overmind then stop now
+ if( self->overmindNode && self->overmindNode->health > 0 )
+ return qtrue;
+
+ //reset parent
+ self->overmindNode = NULL;
+
+ //iterate through entities
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ //if entity is an overmind calculate the distance to it
+ if( ent->s.modelindex == BA_A_OVERMIND && ent->spawned && ent->health > 0 )
+ {
+ self->overmindNode = ent;
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+================
+G_IsOvermindBuilt
+
+Simple wrapper to G_FindOvermind to check if a location has an overmind
+================
+*/
+qboolean G_IsOvermindBuilt( void )
+{
+ gentity_t dummy;
+
+ memset( &dummy, 0, sizeof( gentity_t ) );
+
+ dummy.overmindNode = NULL;
+ dummy.biteam = BIT_ALIENS;
+
+ return G_FindOvermind( &dummy );
+}
+
+/*
+================
+G_FindCreep
+
+attempt to find creep for self, return qtrue if successful
+================
+*/
+static qboolean G_FindCreep( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ gentity_t *closestSpawn = NULL;
+ int distance = 0;
+ int minDistance = 10000;
+ vec3_t temp_v;
+
+ //don't check for creep if flying through the air
+ if( self->s.groundEntityNum == -1 )
+ return qtrue;
+
+ //if self does not have a parentNode or it's parentNode is invalid find a new one
+ if( ( self->parentNode == NULL ) || !self->parentNode->inuse )
+ {
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_A_OVERMIND ) &&
+ ent->spawned )
+ {
+ VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ distance = VectorLength( temp_v );
+ if( distance < minDistance )
+ {
+ closestSpawn = ent;
+ minDistance = distance;
+ }
+ }
+ }
+
+ if( minDistance <= CREEP_BASESIZE )
+ {
+ self->parentNode = closestSpawn;
+ return qtrue;
+ }
+ else
+ return qfalse;
+ }
+
+ //if we haven't returned by now then we must already have a valid parent
+ return qtrue;
+}
+
+/*
+================
+G_IsCreepHere
+
+simple wrapper to G_FindCreep to check if a location has creep
+================
+*/
+static qboolean G_IsCreepHere( vec3_t origin )
+{
+ gentity_t dummy;
+
+ memset( &dummy, 0, sizeof( gentity_t ) );
+
+ dummy.parentNode = NULL;
+ dummy.s.modelindex = BA_NONE;
+ VectorCopy( origin, dummy.s.origin );
+
+ return G_FindCreep( &dummy );
+}
+
+/*
+================
+G_CreepSlow
+
+Set any nearby humans' SS_CREEPSLOWED flag
+================
+*/
+static void G_CreepSlow( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+ buildable_t buildable = self->s.modelindex;
+ float creepSize = (float)BG_FindCreepSizeForBuildable( buildable );
+
+ VectorSet( range, creepSize, creepSize, creepSize );
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //find humans
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->flags & FL_NOTARGET )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ enemy->client->ps.groundEntityNum != ENTITYNUM_NONE &&
+ G_Visible( self, enemy ) )
+ {
+ enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED;
+ enemy->client->lastCreepSlowTime = level.time;
+ }
+ }
+}
+
+/*
+================
+nullDieFunction
+
+hack to prevent compilers complaining about function pointer -> NULL conversion
+================
+*/
+static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+}
+
+/*
+================
+freeBuildable
+================
+*/
+static void freeBuildable( gentity_t *self )
+{
+ G_FreeEntity( self );
+}
+
+
+//==================================================================================
+
+
+
+/*
+================
+A_CreepRecede
+
+Called when an alien spawn dies
+================
+*/
+void A_CreepRecede( gentity_t *self )
+{
+ //if the creep just died begin the recession
+ if( !( self->s.eFlags & EF_DEAD ) )
+ {
+ self->s.eFlags |= EF_DEAD;
+ G_AddEvent( self, EV_BUILD_DESTROY, 0 );
+
+ if( self->spawned )
+ self->s.time = -level.time;
+ else
+ self->s.time = -( level.time -
+ (int)( (float)CREEP_SCALEDOWN_TIME *
+ ( 1.0f - ( (float)( level.time - self->buildTime ) /
+ (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) );
+ }
+
+ //creep is still receeding
+ if( ( self->timestamp + 10000 ) > level.time )
+ self->nextthink = level.time + 500;
+ else //creep has died
+ G_FreeEntity( self );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+ASpawn_Melt
+
+Called when an alien spawn dies
+================
+*/
+void ASpawn_Melt( gentity_t *self )
+{
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //start creep recession
+ if( !( self->s.eFlags & EF_DEAD ) )
+ {
+ self->s.eFlags |= EF_DEAD;
+ G_AddEvent( self, EV_BUILD_DESTROY, 0 );
+
+ if( self->spawned )
+ self->s.time = -level.time;
+ else
+ self->s.time = -( level.time -
+ (int)( (float)CREEP_SCALEDOWN_TIME *
+ ( 1.0f - ( (float)( level.time - self->buildTime ) /
+ (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) );
+ }
+
+ //not dead yet
+ if( ( self->timestamp + 10000 ) > level.time )
+ self->nextthink = level.time + 500;
+ else //dead now
+ G_FreeEntity( self );
+}
+
+/*
+================
+ASpawn_Blast
+
+Called when an alien spawn dies
+================
+*/
+void ASpawn_Blast( gentity_t *self )
+{
+ vec3_t dir;
+
+ VectorCopy( self->s.origin2, dir );
+
+ //do a bit of radius damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //pretty events and item cleanup
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed
+ G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->timestamp = level.time;
+ self->think = ASpawn_Melt;
+ self->nextthink = level.time + 500; //wait .5 seconds before damaging others
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+/*
+================
+ASpawn_Die
+
+Called when an alien spawn dies
+================
+*/
+void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->think = ASpawn_Blast;
+
+ if( self->spawned )
+ self->nextthink = level.time + 5000;
+ else
+ self->nextthink = level.time; //blast immediately
+
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( self->s.modelindex == BA_A_OVERMIND )
+ G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue );
+ else if( self->s.modelindex == BA_A_SPAWN )
+ G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue );
+ }
+ else
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+ }
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+/*
+================
+ASpawn_Think
+
+think function for Alien Spawn
+================
+*/
+void ASpawn_Think( gentity_t *self )
+{
+ gentity_t *ent;
+
+ if( self->spawned )
+ {
+ //only suicide if at rest
+ if( self->s.groundEntityNum )
+ {
+ if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin,
+ self->s.origin2, BA_A_SPAWN, NULL ) ) != NULL )
+ {
+ // If the thing blocking the spawn is a buildable, kill it.
+ // If it's part of the map, kill self.
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue );
+ }
+ else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+ else if( g_antiSpawnBlock.integer && ent->client &&
+ ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //spawnblock protection
+ if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 )
+ {
+ //five seconds of countermeasures and we're still blocked
+ //time for something more drastic
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT );
+ self->spawnBlockTime += 2000;
+ //inappropriate MOD but prints an apt obituary
+ }
+ else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 )
+ //five seconds of blocked by client and...
+ {
+ //random direction
+ vec3_t velocity;
+ velocity[0] = crandom() * g_antiSpawnBlock.integer;
+ velocity[1] = crandom() * g_antiSpawnBlock.integer;
+ velocity[2] = g_antiSpawnBlock.integer;
+
+ VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity );
+ trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" );
+ }
+ else if( !self->spawnBlockTime )
+ self->spawnBlockTime = level.time;
+ }
+ if( ent->s.eType == ET_CORPSE )
+ G_FreeEntity( ent ); //quietly remove
+ }
+ else
+ self->spawnBlockTime = 0;
+ }
+ }
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+/*
+================
+ASpawn_Pain
+
+pain function for Alien Spawn
+================
+*/
+void ASpawn_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
+}
+
+
+
+
+
+//==================================================================================
+
+
+
+
+
+#define OVERMIND_ATTACK_PERIOD 10000
+#define OVERMIND_DYING_PERIOD 5000
+#define OVERMIND_SPAWNS_PERIOD 30000
+
+/*
+================
+AOvermind_Think
+
+Think function for Alien Overmind
+================
+*/
+void AOvermind_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ if( self->spawned && ( self->health > 0 ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->flags & FL_NOTARGET )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ self->timestamp = level.time;
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ }
+ }
+
+ // just in case an egg finishes building after we tell overmind to stfu
+ if( level.numAlienSpawns > 0 )
+ level.overmindMuted = qfalse;
+
+ //low on spawns
+ if( !level.overmindMuted && level.numAlienSpawns <= 0 &&
+ level.time > self->overmindSpawnsTimer )
+ {
+ qboolean haveBuilder = qfalse;
+ gentity_t *builder;
+
+ self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD;
+ G_BroadcastEvent( EV_OVERMIND_SPAWNS, 0 );
+
+ for( i = 0; i < level.numConnectedClients; i++ )
+ {
+ builder = &g_entities[ level.sortedClients[ i ] ];
+ if( builder->health > 0 &&
+ ( builder->client->pers.classSelection == PCL_ALIEN_BUILDER0 ||
+ builder->client->pers.classSelection == PCL_ALIEN_BUILDER0_UPG ) )
+ {
+ haveBuilder = qtrue;
+ break;
+ }
+ }
+ // aliens now know they have no eggs, but they're screwed, so stfu
+ if( !haveBuilder || G_TimeTilSuddenDeath( ) <= 0 )
+ level.overmindMuted = qtrue;
+ }
+
+ //overmind dying
+ if( self->health < ( OVERMIND_HEALTH / 10.0f ) && level.time > self->overmindDyingTimer )
+ {
+ self->overmindDyingTimer = level.time + OVERMIND_DYING_PERIOD;
+ G_BroadcastEvent( EV_OVERMIND_DYING, 0 );
+ }
+
+ //overmind under attack
+ if( self->health < self->lastHealth && level.time > self->overmindAttackTimer )
+ {
+ self->overmindAttackTimer = level.time + OVERMIND_ATTACK_PERIOD;
+ G_BroadcastEvent( EV_OVERMIND_ATTACK, 0 );
+ }
+
+ self->lastHealth = self->health;
+ }
+ else
+ self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD;
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+
+
+//==================================================================================
+
+
+
+
+
+/*
+================
+ABarricade_Pain
+
+pain function for Alien Spawn
+================
+*/
+void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ if( rand( ) % 2 )
+ G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
+ else
+ G_SetBuildableAnim( self, BANIM_PAIN2, qfalse );
+}
+
+/*
+================
+ABarricade_Blast
+
+Called when an alien spawn dies
+================
+*/
+void ABarricade_Blast( gentity_t *self )
+{
+ vec3_t dir;
+
+ VectorCopy( self->s.origin2, dir );
+
+ //do a bit of radius damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //pretty events and item cleanup
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->timestamp = level.time;
+ self->think = A_CreepRecede;
+ self->nextthink = level.time + 500; //wait .5 seconds before damaging others
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+/*
+================
+ABarricade_Die
+
+Called when an alien spawn dies
+================
+*/
+void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->think = ABarricade_Blast;
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( self->spawned )
+ self->nextthink = level.time + 5000;
+ else
+ self->nextthink = level.time; //blast immediately
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+ }
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+/*
+================
+ABarricade_Think
+
+Think function for Alien Barricade
+================
+*/
+void ABarricade_Think( gentity_t *self )
+{
+
+ self->powered = G_IsOvermindBuilt( );
+
+ //if there is no creep nearby die
+ if( !G_FindCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+void AAcidTube_Think( gentity_t *self );
+
+/*
+================
+AAcidTube_Damage
+
+Damage function for Alien Acid Tube
+================
+*/
+void AAcidTube_Damage( gentity_t *self )
+{
+ if( self->spawned )
+ {
+ if( !( self->s.eFlags & EF_FIRING ) )
+ {
+ self->s.eFlags |= EF_FIRING;
+ G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) );
+ }
+
+ if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time )
+ self->think = AAcidTube_Damage;
+ else
+ {
+ self->think = AAcidTube_Think;
+ self->s.eFlags &= ~EF_FIRING;
+ }
+
+ //do some damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+ }
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+/*
+================
+AAcidTube_Think
+
+Think function for Alien Acid Tube
+================
+*/
+void AAcidTube_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+
+ self->powered = G_IsOvermindBuilt( );
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //if there is no creep nearby die
+ if( !G_FindCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->spawned && G_FindOvermind( self ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->flags & FL_NOTARGET )
+ continue;
+
+ if( !G_Visible( self, enemy ) )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( level.paused || enemy->client->pers.paused )
+ continue;
+ self->timestamp = level.time;
+ self->think = AAcidTube_Damage;
+ self->nextthink = level.time + 100;
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ return;
+ }
+ }
+ }
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+AHive_Think
+
+Think function for Alien Hive
+================
+*/
+void AHive_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+ vec3_t dirToTarget;
+
+ self->powered = G_IsOvermindBuilt( );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //if there is no creep nearby die
+ if( !G_FindCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->timestamp < level.time )
+ self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it
+
+ if( self->spawned && !self->active && G_FindOvermind( self ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->flags & FL_NOTARGET )
+ continue;
+
+ if( enemy->health <= 0 )
+ continue;
+
+ if( !G_Visible( self, enemy ) )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( level.paused || enemy->client->pers.paused )
+ continue;
+ self->active = qtrue;
+ self->target_ent = enemy;
+ self->timestamp = level.time + HIVE_REPEAT;
+
+ VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+ VectorNormalize( dirToTarget );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ return;
+ }
+ }
+ }
+
+ G_CreepSlow( self );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+#define HOVEL_TRACE_DEPTH 128.0f
+
+/*
+================
+AHovel_Blocked
+
+Is this hovel entrance blocked?
+================
+*/
+qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit )
+{
+ vec3_t forward, normal, origin, start, end, angles, hovelMaxs;
+ vec3_t mins, maxs;
+ float displacement;
+ trace_t tr;
+
+ BG_FindBBoxForBuildable( BA_A_HOVEL, NULL, hovelMaxs );
+ BG_FindBBoxForClass( player->client->ps.stats[ STAT_PCLASS ],
+ mins, maxs, NULL, NULL, NULL );
+
+ VectorCopy( hovel->s.origin2, normal );
+ AngleVectors( hovel->s.angles, forward, NULL, NULL );
+ VectorInverse( forward );
+
+ displacement = VectorMaxComponent( maxs ) +
+ VectorMaxComponent( hovelMaxs ) + 1.0f;
+
+ VectorMA( hovel->s.origin, displacement, forward, origin );
+
+ VectorCopy( hovel->s.origin, start );
+ VectorCopy( origin, end );
+
+ // see if there's something between the hovel and its exit
+ // (eg built right up against a wall)
+ trap_Trace( &tr, start, NULL, NULL, end, player->s.number, MASK_PLAYERSOLID );
+ if( tr.fraction < 1.0f )
+ return qtrue;
+
+ vectoangles( forward, angles );
+
+ VectorMA( origin, HOVEL_TRACE_DEPTH, normal, start );
+
+ //compute a place up in the air to start the real trace
+ trap_Trace( &tr, origin, mins, maxs, start, player->s.number, MASK_PLAYERSOLID );
+
+ VectorMA( origin, ( HOVEL_TRACE_DEPTH * tr.fraction ) - 1.0f, normal, start );
+ VectorMA( origin, -HOVEL_TRACE_DEPTH, normal, end );
+
+ trap_Trace( &tr, start, mins, maxs, end, player->s.number, MASK_PLAYERSOLID );
+
+ VectorCopy( tr.endpos, origin );
+
+ trap_Trace( &tr, origin, mins, maxs, origin, player->s.number, MASK_PLAYERSOLID );
+
+ if( provideExit )
+ {
+ G_SetOrigin( player, origin );
+ VectorCopy( origin, player->client->ps.origin );
+ // nudge
+ VectorMA( normal, 200.0f, forward, player->client->ps.velocity );
+ G_SetClientViewAngle( player, angles );
+ }
+
+ if( tr.fraction < 1.0f )
+ return qtrue;
+ else
+ return qfalse;
+}
+
+/*
+================
+APropHovel_Blocked
+
+Wrapper to test a hovel placement for validity
+================
+*/
+static qboolean APropHovel_Blocked( vec3_t origin, vec3_t angles, vec3_t normal,
+ gentity_t *player )
+{
+ gentity_t hovel;
+
+ VectorCopy( origin, hovel.s.origin );
+ VectorCopy( angles, hovel.s.angles );
+ VectorCopy( normal, hovel.s.origin2 );
+
+ return AHovel_Blocked( &hovel, player, qfalse );
+}
+
+/*
+================
+AHovel_Use
+
+Called when an alien uses a hovel
+================
+*/
+void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ vec3_t hovelOrigin, hovelAngles, inverseNormal;
+
+ if( self->spawned && G_FindOvermind( self ) )
+ {
+ if( self->active )
+ {
+ //this hovel is in use
+ G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_OCCUPIED );
+ }
+ else if( ( ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ) ||
+ ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) &&
+ activator->health > 0 && self->health > 0 )
+ {
+ if( AHovel_Blocked( self, activator, qfalse ) )
+ {
+ //you can get in, but you can't get out
+ G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_BLOCKED );
+ return;
+ }
+
+ self->active = qtrue;
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+
+ //prevent lerping
+ activator->client->ps.eFlags ^= EF_TELEPORT_BIT;
+ activator->client->ps.eFlags |= EF_NODRAW;
+ G_UnlaggedClear( activator );
+
+ activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING;
+ activator->client->hovel = self;
+ self->builder = activator;
+
+ // Cancel pending suicides
+ activator->suicideTime = 0;
+
+ VectorCopy( self->s.pos.trBase, hovelOrigin );
+ VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin );
+
+ VectorCopy( self->s.origin2, inverseNormal );
+ VectorInverse( inverseNormal );
+ vectoangles( inverseNormal, hovelAngles );
+
+ VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin );
+
+ G_SetOrigin( activator, hovelOrigin );
+ VectorCopy( hovelOrigin, activator->client->ps.origin );
+ G_SetClientViewAngle( activator, hovelAngles );
+ }
+ }
+}
+
+
+/*
+================
+AHovel_Think
+
+Think for alien hovel
+================
+*/
+void AHovel_Think( gentity_t *self )
+{
+ self->powered = G_IsOvermindBuilt( );
+ if( self->spawned )
+ {
+ if( self->active )
+ G_SetIdleBuildableAnim( self, BANIM_IDLE2 );
+ else
+ G_SetIdleBuildableAnim( self, BANIM_IDLE1 );
+ }
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + 200;
+}
+
+/*
+================
+AHovel_Die
+
+Die for alien hovel
+================
+*/
+void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ vec3_t dir;
+
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ VectorCopy( self->s.origin2, dir );
+
+ //do a bit of radius damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //pretty events and item cleanup
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+ self->timestamp = level.time;
+ self->think = ASpawn_Melt;
+ self->nextthink = level.time + 500; //wait .5 seconds before damaging others
+ self->die = nullDieFunction;
+
+ //if the hovel is occupied free the occupant
+ if( self->active )
+ {
+ gentity_t *builder = self->builder;
+ vec3_t newOrigin;
+ vec3_t newAngles;
+
+ VectorCopy( self->s.angles, newAngles );
+ newAngles[ ROLL ] = 0;
+
+ VectorCopy( self->s.origin, newOrigin );
+ VectorMA( newOrigin, 1.0f, self->s.origin2, newOrigin );
+
+ //prevent lerping
+ builder->client->ps.eFlags ^= EF_TELEPORT_BIT;
+ builder->client->ps.eFlags &= ~EF_NODRAW;
+ G_UnlaggedClear( builder );
+
+ G_SetOrigin( builder, newOrigin );
+ VectorCopy( newOrigin, builder->client->ps.origin );
+ G_SetClientViewAngle( builder, newAngles );
+
+ //client leaves hovel
+ builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
+ }
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+ }
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+ABooster_Touch
+
+Called when an alien touches a booster
+================
+*/
+void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ gclient_t *client = other->client;
+
+ if( other->flags & FL_NOTARGET )
+ return; // notarget cancels even beneficial effects?
+
+ if( !self->spawned || self->health <= 0 )
+ return;
+
+ if( !G_FindOvermind( self ) )
+ return;
+
+ if( !client )
+ return;
+
+ if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ return;
+
+ //only allow boostage once every 30 seconds
+ if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time )
+ return;
+
+ if( !( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) )
+ {
+ client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
+ client->lastBoostedTime = level.time;
+ }
+}
+
+
+
+
+//==================================================================================
+
+#define TRAPPER_ACCURACY 10 // lower is better
+
+/*
+================
+ATrapper_FireOnEnemy
+
+Used by ATrapper_Think to fire at enemy
+================
+*/
+void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range )
+{
+ gentity_t *enemy = self->enemy;
+ vec3_t dirToTarget;
+ vec3_t halfAcceleration, thirdJerk;
+ float distanceToTarget = BG_FindRangeForBuildable( self->s.modelindex );
+ int lowMsec = 0;
+ int highMsec = (int)( (
+ ( ( distanceToTarget * LOCKBLOB_SPEED ) +
+ ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) /
+ ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f );
+
+ VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration );
+ VectorScale( enemy->jerk, 1.0f / 3.0f, thirdJerk );
+
+ // highMsec and lowMsec can only move toward
+ // one another, so the loop must terminate
+ while( highMsec - lowMsec > TRAPPER_ACCURACY )
+ {
+ int partitionMsec = ( highMsec + lowMsec ) / 2;
+ float time = (float)partitionMsec / 1000.0f;
+ float projectileDistance = LOCKBLOB_SPEED * time;
+
+ VectorMA( enemy->s.pos.trBase, time, enemy->s.pos.trDelta, dirToTarget );
+ VectorMA( dirToTarget, time * time, halfAcceleration, dirToTarget );
+ VectorMA( dirToTarget, time * time * time, thirdJerk, dirToTarget );
+ VectorSubtract( dirToTarget, self->s.pos.trBase, dirToTarget );
+ distanceToTarget = VectorLength( dirToTarget );
+
+ if( projectileDistance < distanceToTarget )
+ lowMsec = partitionMsec;
+ else if( projectileDistance > distanceToTarget )
+ highMsec = partitionMsec;
+ else if( projectileDistance == distanceToTarget )
+ break; // unlikely to happen
+ }
+
+ VectorNormalize( dirToTarget );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ self->count = level.time + firespeed;
+}
+
+/*
+================
+ATrapper_CheckTarget
+
+Used by ATrapper_Think to check enemies for validity
+================
+*/
+qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range )
+{
+ vec3_t distance;
+ trace_t trace;
+
+ if( !target ) // Do we have a target?
+ return qfalse;
+ if( !target->inuse ) // Does the target still exist?
+ return qfalse;
+ if( target == self ) // is the target us?
+ return qfalse;
+ if( !target->client ) // is the target a bot or player?
+ return qfalse;
+ if( target->flags & FL_NOTARGET ) // is the target cheating?
+ return qfalse;
+ if( target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) // one of us?
+ return qfalse;
+ if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive?
+ return qfalse;
+ if( target->health <= 0 ) // is the target still alive?
+ return qfalse;
+ if( target->client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) // locked?
+ return qfalse;
+
+ VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, distance );
+ if( VectorLength( distance ) > range ) // is the target within range?
+ return qfalse;
+
+ //only allow a narrow field of "vision"
+ VectorNormalize( distance ); //is now direction of target
+ if( DotProduct( distance, self->s.origin2 ) < LOCKBLOB_DOT )
+ return qfalse;
+
+ trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
+ if ( trace.contents & CONTENTS_SOLID ) // can we see the target?
+ return qfalse;
+
+ return qtrue;
+}
+
+/*
+================
+ATrapper_FindEnemy
+
+Used by ATrapper_Think to locate enemy gentities
+================
+*/
+void ATrapper_FindEnemy( gentity_t *ent, int range )
+{
+ gentity_t *target;
+
+ //iterate through entities
+ for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ )
+ {
+ //if target is not valid keep searching
+ if( !ATrapper_CheckTarget( ent, target, range ) )
+ continue;
+
+ //we found a target
+ ent->enemy = target;
+ return;
+ }
+
+ //couldn't find a target
+ ent->enemy = NULL;
+}
+
+/*
+================
+ATrapper_Think
+
+think function for Alien Defense
+================
+*/
+void ATrapper_Think( gentity_t *self )
+{
+ int range = BG_FindRangeForBuildable( self->s.modelindex );
+ int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex );
+
+ self->powered = G_IsOvermindBuilt( );
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //if there is no creep nearby die
+ if( !G_FindCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->spawned && G_FindOvermind( self ) )
+ {
+ //if the current target is not valid find a new one
+ if( !ATrapper_CheckTarget( self, self->enemy, range ) )
+ ATrapper_FindEnemy( self, range );
+
+ //if a new target cannot be found don't do anything
+ if( !self->enemy )
+ return;
+
+ //if we are pointing at our target and we can fire shoot it
+ if( self->count < level.time )
+ ATrapper_FireOnEnemy( self, firespeed, range );
+ }
+}
+
+
+
+//==================================================================================
+
+
+
+/*
+================
+HRepeater_Think
+
+Think for human power repeater
+================
+*/
+void HRepeater_Think( gentity_t *self )
+{
+ int i;
+ qboolean reactor = qfalse;
+ gentity_t *ent;
+
+ if( self->spawned )
+ {
+ //iterate through entities
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent->s.modelindex == BA_H_REACTOR && ent->spawned )
+ reactor = qtrue;
+ }
+ }
+
+ if( G_NumberOfDependants( self ) == 0 )
+ {
+ //if no dependants for x seconds then disappear
+ if( self->count < 0 )
+ self->count = level.time;
+ else if( self->count > 0 && ( ( level.time - self->count ) > REPEATER_INACTIVE_TIME ) )
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ }
+ else
+ self->count = -1;
+
+ self->powered = reactor;
+
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+}
+
+/*
+================
+HRepeater_Use
+
+Use for human power repeater
+================
+*/
+void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( self->health <= 0 )
+ return;
+
+ if( !self->spawned )
+ return;
+
+ if( other )
+ G_GiveClientMaxAmmo( other, qtrue );
+}
+
+
+#define DCC_ATTACK_PERIOD 10000
+
+/*
+================
+HReactor_Think
+
+Think function for Human Reactor
+================
+*/
+void HReactor_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy, *tent;
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ if( self->spawned && ( self->health > 0 ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->flags & FL_NOTARGET )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if( level.paused || enemy->client->pers.paused )
+ continue;
+ self->timestamp = level.time;
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE,
+ REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS );
+
+ tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL );
+
+ VectorCopy( self->s.pos.trBase, tent->s.origin2 );
+
+ tent->s.generic1 = self->s.number; //src
+ tent->s.clientNum = enemy->s.number; //dest
+ }
+ }
+
+ //reactor under attack
+ if( self->health < self->lastHealth &&
+ level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) )
+ {
+ level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
+ G_BroadcastEvent( EV_DCC_ATTACK, 0 );
+ }
+
+ self->lastHealth = self->health;
+ }
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+//==================================================================================
+
+
+
+/*
+================
+HArmoury_Activate
+
+Called when a human activates an Armoury
+================
+*/
+void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( self->spawned )
+ {
+ //only humans can activate this
+ if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ //if this is powered then call the armoury menu
+ if( self->powered )
+ G_TriggerMenu( activator->client->ps.clientNum, MN_H_ARMOURY );
+ else
+ G_TriggerMenu( activator->client->ps.clientNum, MN_H_NOTPOWERED );
+ }
+}
+
+/*
+================
+HArmoury_Think
+
+Think for armoury
+================
+*/
+void HArmoury_Think( gentity_t *self )
+{
+ //make sure we have power
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+
+ self->powered = G_FindPower( self );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+
+/*
+================
+HDCC_Think
+
+Think for dcc
+================
+*/
+void HDCC_Think( gentity_t *self )
+{
+ //make sure we have power
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+
+ self->powered = G_FindPower( self );
+}
+
+
+
+
+//==================================================================================
+
+/*
+================
+HMedistat_Think
+
+think function for Human Medistation
+================
+*/
+void HMedistat_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *player;
+ qboolean occupied = qfalse;
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //make sure we have power
+ if( !( self->powered = G_FindPower( self ) ) )
+ {
+ if( self->active )
+ {
+ G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_IDLE1 );
+ self->active = qfalse;
+ self->enemy = NULL;
+ }
+
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+ return;
+ }
+
+ if( self->spawned )
+ {
+ VectorAdd( self->s.origin, self->r.maxs, maxs );
+ VectorAdd( self->s.origin, self->r.mins, mins );
+
+ mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ];
+ maxs[ 2 ] += 60; //player height
+
+ //if active use the healing idle
+ if( self->active )
+ G_SetIdleBuildableAnim( self, BANIM_IDLE2 );
+
+ //check if a previous occupier is still here
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ player = &g_entities[ entityList[ i ] ];
+
+ if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ player->client->ps.pm_type != PM_DEAD &&
+ self->enemy == player )
+ occupied = qtrue;
+ }
+ }
+
+ if( !occupied )
+ {
+ self->enemy = NULL;
+
+ //look for something to heal
+ for( i = 0; i < num; i++ )
+ {
+ player = &g_entities[ entityList[ i ] ];
+
+ if( player->flags & FL_NOTARGET )
+ continue; // notarget cancels even beneficial effects?
+
+ if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ player->client->ps.pm_type != PM_DEAD )
+ {
+ self->enemy = player;
+
+ //start the heal anim
+ if( !self->active )
+ {
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ self->active = qtrue;
+ }
+ }
+ else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) )
+ BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats );
+ }
+ }
+ }
+
+ //nothing left to heal so go back to idling
+ if( !self->enemy && self->active )
+ {
+ G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_IDLE1 );
+
+ self->active = qfalse;
+ }
+ else if( self->enemy ) //heal!
+ {
+ if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED )
+ self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
+
+ if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE )
+ self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
+
+ self->enemy->health++;
+
+ //if they're completely healed, give them a medkit
+ if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) )
+ BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats );
+
+ // if completely healed, cancel retribution
+ if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] )
+ {
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ self->enemy->client->tkcredits[ i ] = 0;
+ }
+ }
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HMGTurret_TrackEnemy
+
+Used by HMGTurret_Think to track enemy location
+================
+*/
+qboolean HMGTurret_TrackEnemy( gentity_t *self )
+{
+ vec3_t dirToTarget, dttAdjusted, angleToTarget, angularDiff, xNormal;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ float temp, rotAngle;
+ float accuracyTolerance, angularSpeed;
+
+ if( self->lev1Grabbed )
+ {
+ //can't turn fast if grabbed
+ accuracyTolerance = MGTURRET_GRAB_ACCURACYTOLERANCE;
+ angularSpeed = MGTURRET_GRAB_ANGULARSPEED;
+ }
+ else if( self->dcced )
+ {
+ accuracyTolerance = MGTURRET_DCC_ACCURACYTOLERANCE;
+ angularSpeed = MGTURRET_DCC_ANGULARSPEED;
+ }
+ else
+ {
+ accuracyTolerance = MGTURRET_ACCURACYTOLERANCE;
+ angularSpeed = MGTURRET_ANGULARSPEED;
+ }
+
+ VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+
+ VectorNormalize( dirToTarget );
+
+ CrossProduct( self->s.origin2, refNormal, xNormal );
+ VectorNormalize( xNormal );
+ rotAngle = RAD2DEG( acos( DotProduct( self->s.origin2, refNormal ) ) );
+ RotatePointAroundVector( dttAdjusted, xNormal, dirToTarget, rotAngle );
+
+ vectoangles( dttAdjusted, angleToTarget );
+
+ angularDiff[ PITCH ] = AngleSubtract( self->s.angles2[ PITCH ], angleToTarget[ PITCH ] );
+ angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] );
+
+ //if not pointing at our target then move accordingly
+ if( angularDiff[ PITCH ] < (-accuracyTolerance) )
+ self->s.angles2[ PITCH ] += angularSpeed;
+ else if( angularDiff[ PITCH ] > accuracyTolerance )
+ self->s.angles2[ PITCH ] -= angularSpeed;
+ else
+ self->s.angles2[ PITCH ] = angleToTarget[ PITCH ];
+
+ //disallow vertical movement past a certain limit
+ temp = fabs( self->s.angles2[ PITCH ] );
+ if( temp > 180 )
+ temp -= 360;
+
+ if( temp < -MGTURRET_VERTICALCAP )
+ self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP;
+
+ //if not pointing at our target then move accordingly
+ if( angularDiff[ YAW ] < (-accuracyTolerance) )
+ self->s.angles2[ YAW ] += angularSpeed;
+ else if( angularDiff[ YAW ] > accuracyTolerance )
+ self->s.angles2[ YAW ] -= angularSpeed;
+ else
+ self->s.angles2[ YAW ] = angleToTarget[ YAW ];
+
+ AngleVectors( self->s.angles2, dttAdjusted, NULL, NULL );
+ RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //if pointing at our target return true
+ if( fabs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance &&
+ fabs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance )
+ return qtrue;
+
+ return qfalse;
+}
+
+
+/*
+================
+HMGTurret_CheckTarget
+
+Used by HMGTurret_Think to check enemies for validity
+================
+*/
+qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted )
+{
+ trace_t trace;
+ gentity_t *traceEnt;
+
+ if( !target )
+ return qfalse;
+
+ if( target->flags & FL_NOTARGET )
+ return qfalse;
+
+ if( !target->client )
+ return qfalse;
+
+ if( level.paused || target->client->pers.paused )
+ return qfalse;
+
+ if( target->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ return qfalse;
+
+ if( target->health <= 0 )
+ return qfalse;
+
+ if( Distance( self->s.origin, target->s.pos.trBase ) > MGTURRET_RANGE )
+ return qfalse;
+
+ //some turret has already selected this target
+ if( self->dcced && target->targeted && target->targeted->powered && !ignorePainted )
+ return qfalse;
+
+ trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
+
+ traceEnt = &g_entities[ trace.entityNum ];
+
+ if( !traceEnt->client )
+ return qfalse;
+
+ if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+/*
+================
+HMGTurret_FindEnemy
+
+Used by HMGTurret_Think to locate enemy gentities
+================
+*/
+void HMGTurret_FindEnemy( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *target;
+
+ VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE );
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //find aliens
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ target = &g_entities[ entityList[ i ] ];
+
+ if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //if target is not valid keep searching
+ if( !HMGTurret_CheckTarget( self, target, qfalse ) )
+ continue;
+
+ //we found a target
+ self->enemy = target;
+ return;
+ }
+ }
+
+ if( self->dcced )
+ {
+ //check again, this time ignoring painted targets
+ for( i = 0; i < num; i++ )
+ {
+ target = &g_entities[ entityList[ i ] ];
+
+ if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //if target is not valid keep searching
+ if( !HMGTurret_CheckTarget( self, target, qtrue ) )
+ continue;
+
+ //we found a target
+ self->enemy = target;
+ return;
+ }
+ }
+ }
+
+ //couldn't find a target
+ self->enemy = NULL;
+}
+
+#define MGTURRET_DROOPSCALE 0.5f
+#define TURRET_REST_TIME 5000
+#define TURRET_REST_SPEED 3.0f
+#define TURRET_REST_TOLERANCE 4.0f
+
+/*
+================
+HMGTurret_Think
+
+Think function for MG turret
+================
+*/
+void HMGTurret_Think( gentity_t *self )
+{
+ int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //used for client side muzzle flashes
+ self->s.eFlags &= ~EF_FIRING;
+
+ //if not powered don't do anything and check again for power next think
+ if( !( self->powered = G_FindPower( self ) ) )
+ {
+ if( self->spawned )
+ {
+ // unpowered turret barrel falls to bottom of range
+ float droop;
+
+ droop = AngleNormalize180( self->s.angles2[ PITCH ] );
+ if( droop < MGTURRET_VERTICALCAP )
+ {
+ droop += MGTURRET_DROOPSCALE;
+ if( droop > MGTURRET_VERTICALCAP )
+ droop = MGTURRET_VERTICALCAP;
+ self->s.angles2[ PITCH ] = droop;
+ return;
+ }
+ }
+
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+ return;
+ }
+
+ if( self->spawned )
+ {
+ //find a dcc for self
+ self->dcced = G_FindDCC( self );
+
+ //if the current target is not valid find a new one
+ if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) )
+ {
+ if( self->enemy )
+ self->enemy->targeted = NULL;
+
+ HMGTurret_FindEnemy( self );
+ }
+
+ //if a new target cannot be found don't do anything
+ if( !self->enemy )
+ return;
+
+ self->enemy->targeted = self;
+
+ //if we are pointing at our target and we can fire shoot it
+ if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) )
+ {
+ //fire at target
+ FireWeapon( self );
+
+ self->s.eFlags |= EF_FIRING;
+ G_AddEvent( self, EV_FIRE_WEAPON, 0 );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+
+ self->count = level.time + firespeed;
+ }
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HTeslaGen_Think
+
+Think function for Tesla Generator
+================
+*/
+void HTeslaGen_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ vec3_t dir;
+ int i, num;
+ gentity_t *enemy;
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //if not powered don't do anything and check again for power next think
+ if( !( self->powered = G_FindPower( self ) ) || !( self->dcced = G_FindDCC( self ) ) )
+ {
+ self->s.eFlags &= ~EF_FIRING;
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+ return;
+ }
+
+ if( self->spawned && self->count < level.time )
+ {
+ //used to mark client side effects
+ self->s.eFlags &= ~EF_FIRING;
+
+ VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE );
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //find aliens
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->flags & FL_NOTARGET )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ enemy->health > 0 &&
+ !level.paused && !enemy->client->pers.paused &&
+ Distance( enemy->s.pos.trBase, self->s.pos.trBase ) <= TESLAGEN_RANGE )
+ {
+ VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dir );
+ VectorNormalize( dir );
+ vectoangles( dir, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ }
+ }
+
+ if( self->s.eFlags & EF_FIRING )
+ {
+ G_AddEvent( self, EV_FIRE_WEAPON, 0 );
+
+ //doesn't really need an anim
+ //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+
+ self->count = level.time + TESLAGEN_REPEAT;
+ }
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HSpawn_Disappear
+
+Called when a human spawn is destroyed before it is spawned
+think function
+================
+*/
+void HSpawn_Disappear( gentity_t *self )
+{
+ vec3_t dir;
+
+ // we don't have a valid direction, so just point straight up
+ dir[ 0 ] = dir[ 1 ] = 0;
+ dir[ 2 ] = 1;
+
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ self->timestamp = level.time;
+
+ self->think = freeBuildable;
+ self->nextthink = level.time + 100;
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+
+/*
+================
+HSpawn_blast
+
+Called when a human spawn explodes
+think function
+================
+*/
+void HSpawn_Blast( gentity_t *self )
+{
+ vec3_t dir;
+
+ // we don't have a valid direction, so just point straight up
+ dir[ 0 ] = dir[ 1 ] = 0;
+ dir[ 2 ] = 1;
+
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->timestamp = level.time;
+
+ //do some radius damage
+ G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath );
+
+ self->think = freeBuildable;
+ self->nextthink = level.time + 100;
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+
+/*
+================
+HSpawn_die
+
+Called when a human spawn dies
+================
+*/
+void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ //pretty events and cleanup
+ G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->powered = qfalse; //free up power
+ //prevent any firing effects and cancel structure protection
+ self->s.eFlags &= ~( EF_FIRING | EF_DBUILDER );
+
+ if( self->spawned )
+ {
+ self->think = HSpawn_Blast;
+ self->nextthink = level.time + HUMAN_DETONATION_DELAY;
+ }
+ else
+ {
+ self->think = HSpawn_Disappear;
+ self->nextthink = level.time; //blast immediately
+ }
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if( self->s.modelindex == BA_H_REACTOR )
+ G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue );
+ else if( self->s.modelindex == BA_H_SPAWN )
+ G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue );
+ }
+ else
+ {
+ G_TeamCommand( PTE_HUMANS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+ }
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+/*
+================
+HSpawn_Think
+
+Think for human spawn
+================
+*/
+void HSpawn_Think( gentity_t *self )
+{
+ gentity_t *ent;
+
+ // spawns work without power
+ self->powered = qtrue;
+
+ if( self->spawned )
+ {
+ //only suicide if at rest
+ if( self->s.groundEntityNum )
+ {
+ if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin,
+ self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL )
+ {
+ // If the thing blocking the spawn is a buildable, kill it.
+ // If it's part of the map, kill self.
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue );
+ }
+ else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+ else if( g_antiSpawnBlock.integer && ent->client &&
+ ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //spawnblock protection
+ if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 )
+ {
+ //five seconds of countermeasures and we're still blocked
+ //time for something more drastic
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT );
+ self->spawnBlockTime += 2000;
+ //inappropriate MOD but prints an apt obituary
+ }
+ else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 )
+ //five seconds of blocked by client and...
+ {
+ //random direction
+ vec3_t velocity;
+ velocity[0] = crandom() * g_antiSpawnBlock.integer;
+ velocity[1] = crandom() * g_antiSpawnBlock.integer;
+ velocity[2] = g_antiSpawnBlock.integer;
+
+ VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity );
+ trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" );
+ }
+ else if( !self->spawnBlockTime )
+ self->spawnBlockTime = level.time;
+ }
+
+ if( ent->s.eType == ET_CORPSE )
+ G_FreeEntity( ent ); //quietly remove
+ }
+ else
+ self->spawnBlockTime = 0;
+ }
+
+ //spawn under attack
+ if( self->health < self->lastHealth &&
+ level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) )
+ {
+ level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
+ G_BroadcastEvent( EV_DCC_ATTACK, 0 );
+ }
+
+ self->lastHealth = self->health;
+ }
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+//==================================================================================
+
+
+/*
+============
+G_BuildableTouchTriggers
+
+Find all trigger entities that a buildable touches.
+============
+*/
+void G_BuildableTouchTriggers( gentity_t *ent )
+{
+ int i, num;
+ int touch[ MAX_GENTITIES ];
+ gentity_t *hit;
+ trace_t trace;
+ vec3_t mins, maxs;
+ vec3_t bmins, bmaxs;
+ static vec3_t range = { 10, 10, 10 };
+
+ // dead buildables don't activate triggers!
+ if( ent->health <= 0 )
+ return;
+
+ BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs );
+
+ VectorAdd( ent->s.origin, bmins, mins );
+ VectorAdd( ent->s.origin, bmaxs, maxs );
+
+ VectorSubtract( mins, range, mins );
+ VectorAdd( maxs, range, maxs );
+
+ num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
+
+ VectorAdd( ent->s.origin, bmins, mins );
+ VectorAdd( ent->s.origin, bmaxs, maxs );
+
+ for( i = 0; i < num; i++ )
+ {
+ hit = &g_entities[ touch[ i ] ];
+
+ if( !hit->touch )
+ continue;
+
+ if( !( hit->r.contents & CONTENTS_TRIGGER ) )
+ continue;
+
+ //ignore buildables not yet spawned
+ if( !ent->spawned )
+ continue;
+
+ if( !trap_EntityContact( mins, maxs, hit ) )
+ continue;
+
+ memset( &trace, 0, sizeof( trace ) );
+
+ if( hit->touch )
+ hit->touch( hit, ent, &trace );
+ }
+}
+
+
+/*
+===============
+G_BuildableThink
+
+General think function for buildables
+===============
+*/
+void G_BuildableThink( gentity_t *ent, int msec )
+{
+ int bHealth = BG_FindHealthForBuildable( ent->s.modelindex );
+ int bRegen = BG_FindRegenRateForBuildable( ent->s.modelindex );
+ int bTime = BG_FindBuildTimeForBuildable( ent->s.modelindex );
+
+ //pack health, power and dcc
+
+ //toggle spawned flag for buildables
+ if( !ent->spawned && ent->health > 0 )
+ {
+ if( ent->buildTime + bTime < level.time )
+ ent->spawned = qtrue;
+ }
+
+ ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_MASK );
+
+ if( ent->s.generic1 < 0 )
+ ent->s.generic1 = 0;
+
+ if( ent->powered )
+ ent->s.generic1 |= B_POWERED_TOGGLEBIT;
+
+ if( ent->dcced )
+ ent->s.generic1 |= B_DCCED_TOGGLEBIT;
+
+ if( ent->spawned )
+ ent->s.generic1 |= B_SPAWNED_TOGGLEBIT;
+
+ if( ent->deconstruct )
+ ent->s.generic1 |= B_MARKED_TOGGLEBIT;
+
+ ent->time1000 += msec;
+
+ if( ent->time1000 >= 1000 )
+ {
+ ent->time1000 -= 1000;
+
+ if( !ent->spawned && ent->health > 0 )
+ ent->health += (int)( ceil( (float)bHealth / (float)( bTime * 0.001 ) ) );
+ else if( ent->biteam == BIT_ALIENS && ent->health > 0 && ent->health < bHealth &&
+ bRegen && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
+ ent->health += bRegen;
+
+ if( ent->health > bHealth )
+ ent->health = bHealth;
+ }
+
+ if( ent->lev1Grabbed && ent->lev1GrabTime + LEVEL1_GRAB_TIME < level.time )
+ ent->lev1Grabbed = qfalse;
+
+ if( ent->clientSpawnTime > 0 )
+ ent->clientSpawnTime -= msec;
+
+ if( ent->clientSpawnTime < 0 )
+ ent->clientSpawnTime = 0;
+
+ //check if this buildable is touching any triggers
+ G_BuildableTouchTriggers( ent );
+
+ //fall back on normal physics routines
+ G_Physics( ent, msec );
+}
+
+
+/*
+===============
+G_BuildableRange
+
+Check whether a point is within some range of a type of buildable
+===============
+*/
+qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *ent;
+
+ VectorSet( range, r, r, r );
+ VectorAdd( origin, range, maxs );
+ VectorSubtract( origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ ent = &g_entities[ entityList[ i ] ];
+
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent->biteam == BIT_HUMANS && !ent->powered )
+ continue;
+
+ if( ent->s.modelindex == buildable && ent->spawned )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+static qboolean G_BoundsIntersect(const vec3_t mins, const vec3_t maxs,
+ const vec3_t mins2, const vec3_t maxs2)
+{
+ if ( maxs[0] < mins2[0] ||
+ maxs[1] < mins2[1] ||
+ maxs[2] < mins2[2] ||
+ mins[0] > maxs2[0] ||
+ mins[1] > maxs2[1] ||
+ mins[2] > maxs2[2])
+ {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+G_BuildablesIntersect
+
+Test if two buildables intersect each other
+===============
+*/
+static qboolean G_BuildablesIntersect( buildable_t a, vec3_t originA,
+ buildable_t b, vec3_t originB )
+{
+ vec3_t minsA, maxsA;
+ vec3_t minsB, maxsB;
+
+ BG_FindBBoxForBuildable( a, minsA, maxsA );
+ VectorAdd( minsA, originA, minsA );
+ VectorAdd( maxsA, originA, maxsA );
+
+ BG_FindBBoxForBuildable( b, minsB, maxsB );
+ VectorAdd( minsB, originB, minsB );
+ VectorAdd( maxsB, originB, maxsB );
+
+ return G_BoundsIntersect( minsA, maxsA, minsB, maxsB );
+}
+
+/*
+===============
+G_CompareBuildablesForRemoval
+
+qsort comparison function for a buildable removal list
+===============
+*/
+static buildable_t cmpBuildable;
+static vec3_t cmpOrigin;
+static int G_CompareBuildablesForRemoval( const void *a, const void *b )
+{
+ int precedence[ ] =
+ {
+ BA_NONE,
+
+ BA_A_BARRICADE,
+ BA_A_ACIDTUBE,
+ BA_A_TRAPPER,
+ BA_A_HIVE,
+ BA_A_BOOSTER,
+ BA_A_HOVEL,
+ BA_A_SPAWN,
+ BA_A_OVERMIND,
+
+ BA_H_MGTURRET,
+ BA_H_TESLAGEN,
+ BA_H_DCC,
+ BA_H_MEDISTAT,
+ BA_H_ARMOURY,
+ BA_H_SPAWN,
+ BA_H_REPEATER,
+ BA_H_REACTOR
+ };
+
+ gentity_t *buildableA, *buildableB;
+ int i;
+ int aPrecedence = 0, bPrecedence = 0;
+ qboolean aMatches = qfalse, bMatches = qfalse;
+
+ buildableA = *(gentity_t **)a;
+ buildableB = *(gentity_t **)b;
+
+
+ // Prefer the one that collides with the thing we're building
+ aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin,
+ buildableA->s.modelindex, buildableA->s.origin );
+ bMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin,
+ buildableB->s.modelindex, buildableB->s.origin );
+ if( aMatches && !bMatches )
+ return -1;
+ if( !aMatches && bMatches )
+ return 1;
+
+ // If one matches the thing we're building, prefer it
+ aMatches = ( buildableA->s.modelindex == cmpBuildable );
+ bMatches = ( buildableB->s.modelindex == cmpBuildable );
+ if( aMatches && !bMatches )
+ return -1;
+ if( !aMatches && bMatches )
+ return 1;
+
+ // If they're the same type then pick the one marked earliest
+ if( buildableA->s.modelindex == buildableB->s.modelindex )
+ return buildableA->deconstructTime - buildableB->deconstructTime;
+
+ for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ )
+ {
+ if( buildableA->s.modelindex == precedence[ i ] )
+ aPrecedence = i;
+
+ if( buildableB->s.modelindex == precedence[ i ] )
+ bPrecedence = i;
+ }
+
+ return aPrecedence - bPrecedence;
+}
+
+/*
+===============
+G_FreeMarkedBuildables
+
+Free up build points for a team by deconstructing marked buildables
+===============
+*/
+void G_FreeMarkedBuildables( void )
+{
+ int i;
+ gentity_t *ent;
+ buildHistory_t *new, *last;
+ last = level.buildHistory;
+
+ if( !g_markDeconstruct.integer )
+ return; // Not enabled, can't deconstruct anything
+
+ for( i = 0; i < level.numBuildablesForRemoval; i++ )
+ {
+ ent = level.markedBuildables[ i ];
+
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = -1;
+ new->ent = NULL;
+ Q_strncpyz( new->name, "<markdecon>", 12 );
+ new->buildable = ent->s.modelindex;
+ VectorCopy( ent->s.pos.trBase, new->origin );
+ VectorCopy( ent->s.angles, new->angles );
+ VectorCopy( ent->s.origin2, new->origin2 );
+ VectorCopy( ent->s.angles2, new->angles2 );
+ new->fate = BF_DECONNED;
+ new->next = NULL;
+ new->marked = NULL;
+
+ last = last->marked = new;
+
+ G_FreeEntity( ent );
+ }
+}
+
+/*
+===============
+G_SufficientBPAvailable
+
+Determine if enough build points can be released for the buildable
+and list the buildables that much be destroyed if this is the case
+===============
+*/
+static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
+ vec3_t origin )
+{
+ int i;
+ int numBuildables = 0;
+ int pointsYielded = 0;
+ gentity_t *ent;
+ qboolean unique = BG_FindUniqueTestForBuildable( buildable );
+ int remainingBP, remainingSpawns;
+ int team;
+ int buildPoints, buildpointsneeded;
+ qboolean collision = qfalse;
+ int collisionCount = 0;
+ qboolean repeaterInRange = qfalse;
+ int repeaterInRangeCount = 0;
+ itemBuildError_t bpError;
+ buildable_t spawn;
+ buildable_t core;
+ int spawnCount = 0;
+
+ level.numBuildablesForRemoval = 0;
+
+ buildPoints = buildpointsneeded = BG_FindBuildPointsForBuildable( buildable );
+ team = BG_FindTeamForBuildable( buildable );
+ if( team == BIT_ALIENS )
+ {
+ remainingBP = level.alienBuildPoints;
+ remainingSpawns = level.numAlienSpawns;
+ bpError = IBE_NOASSERT;
+ spawn = BA_A_SPAWN;
+ core = BA_A_OVERMIND;
+ }
+ else if( team == BIT_HUMANS )
+ {
+ remainingBP = level.humanBuildPoints;
+ remainingSpawns = level.numHumanSpawns;
+ bpError = IBE_NOPOWER;
+ spawn = BA_H_SPAWN;
+ core = BA_H_REACTOR;
+ }
+ else
+ return IBE_NONE;
+
+ // Simple non-marking case
+ if( !g_markDeconstruct.integer )
+ {
+ if( remainingBP - buildPoints < 0 )
+ return bpError;
+
+ // Check for buildable<->buildable collisions
+ for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin ) )
+ return IBE_NOROOM;
+ }
+
+ return IBE_NONE;
+ }
+
+ buildpointsneeded -= remainingBP;
+
+ // Set buildPoints to the number extra that are required
+ if( !g_markDeconstructMode.integer )
+ buildPoints -= remainingBP;
+
+ // Build a list of buildable entities
+ for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ collision = G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin );
+
+ if( collision )
+ collisionCount++;
+
+ // Check if this is a repeater and it's in range
+ if( buildable == BA_H_REPEATER &&
+ buildable == ent->s.modelindex &&
+ Distance( ent->s.origin, origin ) < REPEATER_BASESIZE )
+ {
+ repeaterInRange = qtrue;
+ repeaterInRangeCount++;
+ }
+ else
+ repeaterInRange = qfalse;
+
+ if( !ent->inuse )
+ continue;
+
+ if( ent->health <= 0 )
+ continue;
+
+ if( ent->biteam != team )
+ continue;
+
+ // Don't allow destruction of hovel with granger inside
+ if( ent->s.modelindex == BA_A_HOVEL && ent->active )
+ {
+ if( buildable == BA_A_HOVEL )
+ return IBE_HOVEL;
+ else
+ continue;
+ }
+
+ // Explicitly disallow replacement of the core buildable with anything
+ // other than the core buildable
+ if( ent->s.modelindex == core && buildable != core )
+ continue;
+
+ if( ent->deconstruct )
+ {
+ level.markedBuildables[ numBuildables++ ] = ent;
+
+ if( collision || repeaterInRange )
+ {
+ if( collision )
+ collisionCount--;
+
+ if( repeaterInRange )
+ repeaterInRangeCount--;
+
+ pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ level.numBuildablesForRemoval++;
+ }
+ else if( unique && ent->s.modelindex == buildable )
+ {
+ // If it's a unique buildable, it must be replaced by the same type
+ pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ level.numBuildablesForRemoval++;
+ }
+ }
+ }
+
+ // We still need build points, but have no candidates for removal
+ if( buildpointsneeded > 0 && numBuildables == 0 )
+ return bpError;
+
+ // Collided with something we can't remove
+ if( collisionCount > 0 )
+ return IBE_NOROOM;
+
+ // There are one or more repeaters we can't remove
+ if( repeaterInRangeCount > 0 )
+ return IBE_RPTWARN2;
+
+ // Sort the list
+ cmpBuildable = buildable;
+ VectorCopy( origin, cmpOrigin );
+ qsort( level.markedBuildables, numBuildables, sizeof( level.markedBuildables[ 0 ] ),
+ G_CompareBuildablesForRemoval );
+
+ // Determine if there are enough markees to yield the required BP
+ for( ; pointsYielded < buildPoints && level.numBuildablesForRemoval < numBuildables;
+ level.numBuildablesForRemoval++ )
+ {
+ ent = level.markedBuildables[ level.numBuildablesForRemoval ];
+ pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ }
+
+ // Make sure we're not removing the last spawn
+ for( i = 0; i < level.numBuildablesForRemoval; i++ )
+ {
+ if( level.markedBuildables[ i ]->s.modelindex == spawn )
+ spawnCount++;
+ }
+ if( !g_cheats.integer && remainingSpawns > 0 && ( remainingSpawns - spawnCount ) < 1 )
+ return IBE_NORMAL;
+
+ // Not enough points yielded
+ if( pointsYielded < buildpointsneeded )
+ return bpError;
+
+ return IBE_NONE;
+}
+
+/*
+================
+G_SetBuildableLinkState
+
+Links or unlinks all the buildable entities
+================
+*/
+static void G_SetBuildableLinkState( qboolean link )
+{
+ int i;
+ gentity_t *ent;
+
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( link )
+ trap_LinkEntity( ent );
+ else
+ trap_UnlinkEntity( ent );
+ }
+}
+
+static void G_SetBuildableMarkedLinkState( qboolean link )
+{
+ int i;
+ gentity_t *ent;
+
+ for( i = 0; i < level.numBuildablesForRemoval; i++ )
+ {
+ ent = level.markedBuildables[ i ];
+ if( link )
+ trap_LinkEntity( ent );
+ else
+ trap_UnlinkEntity( ent );
+ }
+}
+
+/*
+================
+G_CanBuild
+
+Checks to see if a buildable can be built
+================
+*/
+itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin )
+{
+ vec3_t angles;
+ vec3_t entity_origin, normal;
+ vec3_t mins, maxs, nbmins, nbmaxs;
+ vec3_t nbVect;
+ trace_t tr1, tr2, tr3;
+ int i;
+ itemBuildError_t reason = IBE_NONE;
+ gentity_t *tempent;
+ float minNormal;
+ qboolean invert;
+ int contents;
+ playerState_t *ps = &ent->client->ps;
+ int buildPoints;
+ gentity_t *tmp;
+ itemBuildError_t tempReason;
+
+ // Stop all buildables from interacting with traces
+ G_SetBuildableLinkState( qfalse );
+
+ BG_FindBBoxForBuildable( buildable, mins, maxs );
+
+ BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, trap_Trace, entity_origin, angles, &tr1 );
+
+ trap_Trace( &tr2, entity_origin, mins, maxs, entity_origin, ent->s.number, MASK_PLAYERSOLID );
+ trap_Trace( &tr3, ps->origin, NULL, NULL, entity_origin, ent->s.number, MASK_PLAYERSOLID );
+
+ VectorCopy( entity_origin, origin );
+
+ VectorCopy( tr1.plane.normal, normal );
+ minNormal = BG_FindMinNormalForBuildable( buildable );
+ invert = BG_FindInvertNormalForBuildable( buildable );
+
+ //can we build at this angle?
+ if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) )
+ reason = IBE_NORMAL;
+
+ if( tr1.entityNum != ENTITYNUM_WORLD )
+ reason = IBE_NORMAL;
+
+ contents = trap_PointContents( entity_origin, -1 );
+ buildPoints = BG_FindBuildPointsForBuildable( buildable );
+
+ //check if we are near a nobuild marker, if so, can't build here...
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ tmp = &g_entities[ i ];
+
+ if( !tmp->noBuild.isNB )
+ continue;
+
+ nbVect[0] = tmp->noBuild.Area;
+ nbVect[1] = tmp->noBuild.Area;
+ nbVect[2] = tmp->noBuild.Height;
+
+ VectorSubtract( origin, nbVect, nbmins );
+ VectorAdd( origin, nbVect, nbmaxs );
+
+ if( trap_EntityContact( nbmins, nbmaxs, tmp ) )
+ reason = IBE_PERMISSION;
+
+ }
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //alien criteria
+
+ if( buildable == BA_A_HOVEL )
+ {
+ vec3_t builderMins, builderMaxs;
+
+ //this assumes the adv builder is the biggest thing that'll use the hovel
+ BG_FindBBoxForClass( PCL_ALIEN_BUILDER0_UPG, builderMins, builderMaxs, NULL, NULL, NULL );
+
+ if( APropHovel_Blocked( origin, angles, normal, ent ) )
+ reason = IBE_HOVELEXIT;
+ }
+
+ //check there is creep near by for building on
+ if( BG_FindCreepTestForBuildable( buildable ) )
+ {
+ if( !G_IsCreepHere( entity_origin ) )
+ reason = IBE_NOCREEP;
+ }
+
+ //check permission to build here
+ if( tr1.surfaceFlags & SURF_NOALIENBUILD || tr1.surfaceFlags & SURF_NOBUILD ||
+ contents & CONTENTS_NOALIENBUILD || contents & CONTENTS_NOBUILD )
+ reason = IBE_PERMISSION;
+
+ //look for an Overmind
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+ if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned &&
+ tempent->health > 0 )
+ break;
+ }
+
+ //if none found...
+ if( i >= level.num_entities && buildable != BA_A_OVERMIND )
+ reason = IBE_NOOVERMIND;
+
+ //can we only have one of these?
+ if( BG_FindUniqueTestForBuildable( buildable ) )
+ {
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == buildable && !tempent->deconstruct )
+ {
+ switch( buildable )
+ {
+ case BA_A_OVERMIND:
+ reason = IBE_OVERMIND;
+ break;
+
+ case BA_A_HOVEL:
+ reason = IBE_HOVEL;
+ break;
+
+ default:
+ Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable );
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //human criteria
+ if( !G_IsPowered( entity_origin ) )
+ {
+ //tell player to build a repeater to provide power
+ if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER )
+ reason = IBE_REPEATER;
+ }
+
+ //this buildable requires a DCC
+ if( BG_FindDCCTestForBuildable( buildable ) && !G_IsDCCBuilt( ) )
+ reason = IBE_NODCC;
+
+ //check that there is a parent reactor when building a repeater
+ if( buildable == BA_H_REPEATER )
+ {
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == BA_H_REACTOR )
+ break;
+ }
+
+ if( i >= level.num_entities )
+ {
+ //no reactor present
+
+ //check for other nearby repeaters
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == BA_H_REPEATER &&
+ Distance( tempent->s.origin, entity_origin ) < REPEATER_BASESIZE )
+ {
+ reason = IBE_RPTWARN2;
+ break;
+ }
+ }
+
+ if( reason == IBE_NONE )
+ reason = IBE_RPTWARN;
+ }
+ else if( G_IsPowered( entity_origin ) )
+ reason = IBE_RPTWARN2;
+ }
+
+ //check permission to build here
+ if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD ||
+ contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD )
+ reason = IBE_PERMISSION;
+
+ //can we only build one of these?
+ if( BG_FindUniqueTestForBuildable( buildable ) )
+ {
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct )
+ {
+ reason = IBE_REACTOR;
+ break;
+ }
+ }
+ }
+ }
+
+ if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE )
+ reason = tempReason;
+
+ // Relink buildables
+ G_SetBuildableLinkState( qtrue );
+
+ //check there is enough room to spawn from (presuming this is a spawn)
+ if( reason == IBE_NONE )
+ {
+ G_SetBuildableMarkedLinkState( qfalse );
+ if( G_CheckSpawnPoint( ENTITYNUM_NONE, origin, normal, buildable, NULL ) != NULL )
+ reason = IBE_NORMAL;
+ G_SetBuildableMarkedLinkState( qtrue );
+ }
+
+ //this item does not fit here
+ if( reason == IBE_NONE && ( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) )
+ return IBE_NOROOM;
+
+ if( reason != IBE_NONE )
+ level.numBuildablesForRemoval = 0;
+
+ return reason;
+}
+
+/*
+==============
+G_BuildingExists
+==============
+*/
+qboolean G_BuildingExists( int bclass )
+{
+ int i;
+ gentity_t *tempent;
+ //look for an Armoury
+ for (i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+ if( tempent->s.modelindex == bclass && tempent->health > 0 )
+ {
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+
+/*
+================
+G_Build
+
+Spawns a buildable
+================
+*/
+static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles )
+{
+ gentity_t *built;
+ buildHistory_t *new;
+ vec3_t normal;
+
+ // initialise the buildhistory so other functions can use it
+ if( builder && builder->client )
+ {
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ G_LogBuild( new );
+ }
+
+ // Free existing buildables
+ G_FreeMarkedBuildables( );
+
+ //spawn the buildable
+ built = G_Spawn();
+
+ built->s.eType = ET_BUILDABLE;
+
+ built->classname = BG_FindEntityNameForBuildable( buildable );
+
+ built->s.modelindex = buildable; //so we can tell what this is on the client side
+ built->biteam = built->s.modelindex2 = BG_FindTeamForBuildable( buildable );
+
+ BG_FindBBoxForBuildable( buildable, built->r.mins, built->r.maxs );
+
+ // detect the buildable's normal vector
+ if( !builder->client )
+ {
+ // initial base layout created by server
+
+ if( builder->s.origin2[ 0 ]
+ || builder->s.origin2[ 1 ]
+ || builder->s.origin2[ 2 ] )
+ {
+ VectorCopy( builder->s.origin2, normal );
+ }
+ else if( BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+ }
+ else
+ {
+ // in-game building by a player
+
+ if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( builder->client->ps.grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+ }
+
+ // when building the initial layout, spawn the entity slightly off its
+ // target surface so that it can be "dropped" onto it
+ if( !builder->client )
+ VectorMA( origin, 1.0f, normal, origin );
+
+ built->health = 1;
+
+ built->splashDamage = BG_FindSplashDamageForBuildable( buildable );
+ built->splashRadius = BG_FindSplashRadiusForBuildable( buildable );
+ built->splashMethodOfDeath = BG_FindMODForBuildable( buildable );
+
+ built->nextthink = BG_FindNextThinkForBuildable( buildable );
+
+ built->takedamage = qtrue;
+ built->spawned = qfalse;
+ built->buildTime = built->s.time = level.time;
+ built->spawnBlockTime = 0;
+
+ // build instantly in cheat mode
+ if( builder->client && g_cheats.integer )
+ {
+ built->health = BG_FindHealthForBuildable( buildable );
+ built->buildTime = built->s.time =
+ level.time - BG_FindBuildTimeForBuildable( buildable );
+ }
+
+ //things that vary for each buildable that aren't in the dbase
+ switch( buildable )
+ {
+ case BA_A_SPAWN:
+ built->die = ASpawn_Die;
+ built->think = ASpawn_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_BARRICADE:
+ built->die = ABarricade_Die;
+ built->think = ABarricade_Think;
+ built->pain = ABarricade_Pain;
+ break;
+
+ case BA_A_BOOSTER:
+ built->die = ABarricade_Die;
+ built->think = ABarricade_Think;
+ built->pain = ABarricade_Pain;
+ built->touch = ABooster_Touch;
+ break;
+
+ case BA_A_ACIDTUBE:
+ built->die = ABarricade_Die;
+ built->think = AAcidTube_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_HIVE:
+ built->die = ABarricade_Die;
+ built->think = AHive_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_TRAPPER:
+ built->die = ABarricade_Die;
+ built->think = ATrapper_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_OVERMIND:
+ built->die = ASpawn_Die;
+ built->think = AOvermind_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_HOVEL:
+ built->die = AHovel_Die;
+ built->use = AHovel_Use;
+ built->think = AHovel_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_H_SPAWN:
+ built->die = HSpawn_Die;
+ built->think = HSpawn_Think;
+ break;
+
+ case BA_H_MGTURRET:
+ built->die = HSpawn_Die;
+ built->think = HMGTurret_Think;
+ break;
+
+ case BA_H_TESLAGEN:
+ built->die = HSpawn_Die;
+ built->think = HTeslaGen_Think;
+ break;
+
+ case BA_H_ARMOURY:
+ built->think = HArmoury_Think;
+ built->die = HSpawn_Die;
+ built->use = HArmoury_Activate;
+ break;
+
+ case BA_H_DCC:
+ built->think = HDCC_Think;
+ built->die = HSpawn_Die;
+ break;
+
+ case BA_H_MEDISTAT:
+ built->think = HMedistat_Think;
+ built->die = HSpawn_Die;
+ break;
+
+ case BA_H_REACTOR:
+ built->think = HReactor_Think;
+ built->die = HSpawn_Die;
+ built->use = HRepeater_Use;
+ built->powered = built->active = qtrue;
+ break;
+
+ case BA_H_REPEATER:
+ built->think = HRepeater_Think;
+ built->die = HSpawn_Die;
+ built->use = HRepeater_Use;
+ built->count = -1;
+ break;
+
+ default:
+ //erk
+ break;
+ }
+
+ built->s.number = built - g_entities;
+ built->r.contents = CONTENTS_BODY;
+ built->clipmask = MASK_PLAYERSOLID;
+ built->enemy = NULL;
+ built->s.weapon = BG_FindProjTypeForBuildable( buildable );
+
+ if( builder->client )
+ {
+ built->builtBy = builder->client->ps.clientNum;
+
+ if( builder->client->pers.designatedBuilder )
+ {
+ built->s.eFlags |= EF_DBUILDER; // designated builder protection
+ }
+ }
+ else
+ built->builtBy = -1;
+
+ G_SetOrigin( built, origin );
+
+ // gently nudge the buildable onto the surface :)
+ VectorScale( normal, -50.0f, built->s.pos.trDelta );
+
+ // set turret angles
+ VectorCopy( builder->s.angles2, built->s.angles2 );
+
+ VectorCopy( angles, built->s.angles );
+ built->s.angles[ PITCH ] = 0.0f;
+ built->s.angles2[ YAW ] = angles[ YAW ];
+ built->s.pos.trType = BG_FindTrajectoryForBuildable( buildable );
+ built->s.pos.trTime = level.time;
+ built->physicsBounce = BG_FindBounceForBuildable( buildable );
+ built->s.groundEntityNum = -1;
+
+ built->s.generic1 = (int)( ( (float)built->health /
+ (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK );
+
+ if( built->s.generic1 < 0 )
+ built->s.generic1 = 0;
+
+ if( BG_FindTeamForBuildable( built->s.modelindex ) == PTE_ALIENS )
+ {
+ built->powered = qtrue;
+ built->s.generic1 |= B_POWERED_TOGGLEBIT;
+ }
+ else if( ( built->powered = G_FindPower( built ) ) )
+ built->s.generic1 |= B_POWERED_TOGGLEBIT;
+
+ if( ( built->dcced = G_FindDCC( built ) ) )
+ built->s.generic1 |= B_DCCED_TOGGLEBIT;
+
+ built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT;
+
+ VectorCopy( normal, built->s.origin2 );
+
+ G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 );
+
+ G_SetIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) );
+
+ if( built->builtBy >= 0 )
+ G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue );
+
+ trap_LinkEntity( built );
+
+
+ if( builder->client )
+ {
+ builder->client->pers.statscounters.structsbuilt++;
+ if( builder->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.structsbuilt++;
+ }
+ else if( builder->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.structsbuilt++;
+ }
+ }
+
+ if( builder->client ) {
+ G_TeamCommand( builder->client->pers.teamSelection,
+ va( "print \"%s is ^2being built^7 by %s^7\n\"",
+ BG_FindHumanNameForBuildable( built->s.modelindex ),
+ builder->client->pers.netname ) );
+ G_LogPrintf("Build: %i %i 0: %s^7 is ^2building^7 %s\n",
+ builder->client->ps.clientNum,
+ built->s.modelindex,
+ builder->client->pers.netname,
+ BG_FindNameForBuildable( built->s.modelindex ) );
+ }
+
+ // ok we're all done building, so what we log here should be the final values
+ if( builder && builder->client ) // log ingame building only
+ {
+ new = level.buildHistory;
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = builder;
+ new->name[ 0 ] = 0;
+ new->buildable = buildable;
+ VectorCopy( built->s.pos.trBase, new->origin );
+ VectorCopy( built->s.angles, new->angles );
+ VectorCopy( built->s.origin2, new->origin2 );
+ VectorCopy( built->s.angles2, new->angles2 );
+ new->fate = BF_BUILT;
+ }
+
+ if( builder && builder->client )
+ built->bdnumb = new->ID;
+ else
+ built->bdnumb = -1;
+
+ return built;
+}
+
+static void G_SpawnMarker( vec3_t origin )
+{
+ gentity_t *nb;
+ int i;
+
+ // Make the marker...
+ nb = G_Spawn( );
+ nb->s.modelindex = 0; //Coder humor is win
+ VectorCopy( origin, nb->s.pos.trBase );
+ VectorCopy( origin, nb->r.currentOrigin );
+ nb->noBuild.isNB = qtrue;
+ nb->noBuild.Area = level.nbArea;
+ nb->noBuild.Height = level.nbHeight;
+ trap_LinkEntity( nb );
+
+ // Log markers made...
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ if( level.nbMarkers[ i ].Marker != NULL )
+ continue;
+
+ level.nbMarkers[ i ].Marker = nb;
+ VectorCopy( origin, level.nbMarkers[ i ].Origin );
+ SnapVector( level.nbMarkers[ i ].Origin );
+ break;
+ }
+
+ // End nobuild mode...
+ level.noBuilding = qfalse;
+ level.nbArea = 0.0f;
+ level.nbHeight = 0.0f;
+}
+
+/*
+=================
+G_BuildIfValid
+=================
+*/
+qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable )
+{
+ float dist;
+ vec3_t origin;
+
+ dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+
+ switch( G_CanBuild( ent, buildable, dist, origin ) )
+ {
+ case IBE_NONE:
+ if( level.noBuilding )
+ {
+ vec3_t mins;
+ BG_FindBBoxForBuildable( buildable, mins, NULL );
+ origin[2] += mins[2];
+
+ G_SpawnMarker( origin );
+ return qtrue;
+ }
+ G_Build( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_NOASSERT:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT );
+ return qfalse;
+
+ case IBE_NOOVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND );
+ return qfalse;
+
+ case IBE_NOCREEP:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );
+ return qfalse;
+
+ case IBE_OVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND );
+ return qfalse;
+
+ case IBE_HOVEL:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL );
+ return qfalse;
+
+ case IBE_HOVELEXIT:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_EXIT );
+ return qfalse;
+
+ case IBE_NORMAL:
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL );
+ else
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL );
+ return qfalse;
+
+ case IBE_PERMISSION:
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL );
+ else
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL );
+ return qfalse;
+
+ case IBE_REACTOR:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR );
+ return qfalse;
+
+ case IBE_REPEATER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER );
+ return qfalse;
+
+ case IBE_NOROOM:
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOM );
+ else
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOROOM );
+ return qfalse;
+
+ case IBE_NOPOWER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER );
+ return qfalse;
+
+ case IBE_NODCC:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );
+ return qfalse;
+
+ case IBE_SPWNWARN:
+ if( level.noBuilding )
+ {
+ vec3_t mins;
+ BG_FindBBoxForBuildable( buildable, mins, NULL );
+ origin[2] += mins[2];
+ G_SpawnMarker( origin );
+ return qtrue;
+ }
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN );
+ G_Build( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_TNODEWARN:
+ if( level.noBuilding )
+ {
+ vec3_t mins;
+ BG_FindBBoxForBuildable( buildable, mins, NULL );
+ origin[2] += mins[2];
+ G_SpawnMarker( origin );
+ return qtrue;
+ }
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN );
+ G_Build( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_RPTWARN:
+ if( level.noBuilding )
+ {
+ vec3_t mins;
+ BG_FindBBoxForBuildable( buildable, mins, NULL );
+ origin[2] += mins[2];
+ G_SpawnMarker( origin );
+ return qtrue;
+ }
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN );
+ G_Build( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_RPTWARN2:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 );
+ return qfalse;
+
+ default:
+ break;
+ }
+
+ return qfalse;
+}
+
+/*
+================
+G_FinishSpawningBuildable
+
+Traces down to find where an item should rest, instead of letting them
+free fall from their spawn points
+================
+*/
+static void G_FinishSpawningBuildable( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t dest;
+ gentity_t *built;
+ buildable_t buildable = ent->s.modelindex;
+
+ built = G_Build( ent, buildable, ent->s.pos.trBase, ent->s.angles );
+ G_FreeEntity( ent );
+
+ built->takedamage = qtrue;
+ built->spawned = qtrue; //map entities are already spawned
+ built->health = BG_FindHealthForBuildable( buildable );
+ built->s.generic1 |= B_SPAWNED_TOGGLEBIT;
+
+ // drop towards normal surface
+ VectorScale( built->s.origin2, -4096.0f, dest );
+ VectorAdd( dest, built->s.origin, dest );
+
+ trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask );
+
+ if( tr.startsolid )
+ {
+ G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n",
+ built->classname, vtos( built->s.origin ) );
+ G_FreeEntity( built );
+ return;
+ }
+
+ //point items in the correct direction
+ VectorCopy( tr.plane.normal, built->s.origin2 );
+
+ // allow to ride movers
+ built->s.groundEntityNum = tr.entityNum;
+
+ G_SetOrigin( built, tr.endpos );
+
+ trap_LinkEntity( built );
+}
+
+/*
+============
+G_SpawnBuildable
+
+Sets the clipping size and plants the object on the floor.
+
+Items can't be immediately dropped to floor, because they might
+be on an entity that hasn't spawned yet.
+============
+*/
+void G_SpawnBuildable( gentity_t *ent, buildable_t buildable )
+{
+ ent->s.modelindex = buildable;
+
+ // some movers spawn on the second frame, so delay item
+ // spawns until the third frame so they can ride trains
+ ent->nextthink = level.time + FRAMETIME * 2;
+ ent->think = G_FinishSpawningBuildable;
+}
+
+ /*
+ ============
+ G_CheckDBProtection
+
+ Count how many designated builders are in both teams and
+ if none found in some team, cancel protection for all
+ structures of that team
+ ============
+ */
+
+ void G_CheckDBProtection( void )
+ {
+ int alienDBs = 0, humanDBs = 0, i;
+ gentity_t *ent;
+
+ // count designated builders
+ for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++)
+ {
+ if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) )
+ continue;
+
+ if( ent->client->pers.designatedBuilder)
+ {
+ if( ent->client->pers.teamSelection == PTE_HUMANS )
+ {
+ humanDBs++;
+ }
+ else if( ent->client->pers.teamSelection == PTE_ALIENS )
+ {
+ alienDBs++;
+ }
+ }
+ }
+
+ // both teams have designate builders, we're done
+ if( alienDBs > 0 && humanDBs > 0 )
+ return;
+
+ // cancel protection if needed
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++)
+ {
+ if( ent->s.eType != ET_BUILDABLE)
+ continue;
+
+ if( ( !alienDBs && ent->biteam == BIT_ALIENS ) ||
+ ( !humanDBs && ent->biteam == BIT_HUMANS ) )
+ {
+ ent->s.eFlags &= ~EF_DBUILDER;
+ }
+ }
+ }
+
+/*
+============
+G_LayoutSave
+
+============
+*/
+void G_LayoutSave( char *name )
+{
+ char map[ MAX_QPATH ];
+ char fileName[ MAX_OSPATH ];
+ fileHandle_t f;
+ int len;
+ int i;
+ gentity_t *ent;
+ char *s;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ if( !map[ 0 ] )
+ {
+ G_Printf( "LayoutSave( ): no map is loaded\n" );
+ return;
+ }
+ Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name );
+
+ len = trap_FS_FOpenFile( fileName, &f, FS_WRITE );
+ if( len < 0 )
+ {
+ G_Printf( "layoutsave: could not open %s\n", fileName );
+ return;
+ }
+
+ G_Printf("layoutsave: saving layout to %s\n", fileName );
+
+ for( i = MAX_CLIENTS; i < level.num_entities; i++ )
+ {
+ ent = &level.gentities[ i ];
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ s = va( "%i %f %f %f %f %f %f %f %f %f %f %f %f\n",
+ ent->s.modelindex,
+ ent->s.pos.trBase[ 0 ],
+ ent->s.pos.trBase[ 1 ],
+ ent->s.pos.trBase[ 2 ],
+ ent->s.angles[ 0 ],
+ ent->s.angles[ 1 ],
+ ent->s.angles[ 2 ],
+ ent->s.origin2[ 0 ],
+ ent->s.origin2[ 1 ],
+ ent->s.origin2[ 2 ],
+ ent->s.angles2[ 0 ],
+ ent->s.angles2[ 1 ],
+ ent->s.angles2[ 2 ] );
+ trap_FS_Write( s, strlen( s ), f );
+ }
+ trap_FS_FCloseFile( f );
+}
+
+int G_LayoutList( const char *map, char *list, int len )
+{
+ // up to 128 single character layout names could fit in layouts
+ char fileList[ ( MAX_CVAR_VALUE_STRING / 2 ) * 5 ] = {""};
+ char layouts[ MAX_CVAR_VALUE_STRING ] = {""};
+ int numFiles, i, fileLen = 0, listLen;
+ int count = 0;
+ char *filePtr;
+
+ Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " );
+ numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat",
+ fileList, sizeof( fileList ) );
+ filePtr = fileList;
+ for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ listLen = strlen( layouts );
+ if( fileLen < 5 )
+ continue;
+
+ // list is full, stop trying to add to it
+ if( ( listLen + fileLen ) >= sizeof( layouts ) )
+ break;
+
+ Q_strcat( layouts, sizeof( layouts ), filePtr );
+ listLen = strlen( layouts );
+
+ // strip extension and add space delimiter
+ layouts[ listLen - 4 ] = ' ';
+ layouts[ listLen - 3 ] = '\0';
+ count++;
+ }
+ if( count != numFiles )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: layout list was truncated to %d "
+ "layouts, but %d layout files exist in layouts/%s/.\n",
+ count, numFiles, map );
+ }
+ Q_strncpyz( list, layouts, len );
+ return count + 1;
+}
+
+/*
+============
+G_LayoutSelect
+
+set level.layout based on g_layouts or g_layoutAuto
+============
+*/
+void G_LayoutSelect( void )
+{
+ char fileName[ MAX_OSPATH ];
+ char layouts[ MAX_CVAR_VALUE_STRING ];
+ char layouts2[ MAX_CVAR_VALUE_STRING ];
+ char *l;
+ char map[ MAX_QPATH ];
+ char *s;
+ int cnt = 0;
+ int layoutNum;
+
+ Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) );
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ // one time use cvar
+ trap_Cvar_Set( "g_layouts", "" );
+
+ // pick an included layout at random if no list has been provided
+ if( !layouts[ 0 ] && g_layoutAuto.integer )
+ {
+ G_LayoutList( map, layouts, sizeof( layouts ) );
+ }
+
+ if( !layouts[ 0 ] )
+ return;
+
+ Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );
+ l = &layouts2[ 0 ];
+ layouts[ 0 ] = '\0';
+ s = COM_ParseExt( &l, qfalse );
+ while( *s )
+ {
+ if( !Q_stricmp( s, "*BUILTIN*" ) )
+ {
+ Q_strcat( layouts, sizeof( layouts ), s );
+ Q_strcat( layouts, sizeof( layouts ), " " );
+ cnt++;
+ s = COM_ParseExt( &l, qfalse );
+ continue;
+ }
+
+ Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, s );
+ if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) > 0 )
+ {
+ Q_strcat( layouts, sizeof( layouts ), s );
+ Q_strcat( layouts, sizeof( layouts ), " " );
+ cnt++;
+ }
+ else
+ G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s );
+ s = COM_ParseExt( &l, qfalse );
+ }
+ if( !cnt )
+ {
+ G_Printf( S_COLOR_RED "ERROR: none of the specified layouts could be "
+ "found, using map default\n" );
+ return;
+ }
+ layoutNum = ( rand( ) % cnt ) + 1;
+ cnt = 0;
+
+ Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );
+ l = &layouts2[ 0 ];
+ s = COM_ParseExt( &l, qfalse );
+ while( *s )
+ {
+ Q_strncpyz( level.layout, s, sizeof( level.layout ) );
+ cnt++;
+ if( cnt >= layoutNum )
+ break;
+ s = COM_ParseExt( &l, qfalse );
+ }
+ G_Printf("using layout \"%s\" from list ( %s)\n", level.layout, layouts );
+}
+
+static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin,
+ vec3_t angles, vec3_t origin2, vec3_t angles2 )
+{
+ gentity_t *builder;
+
+ builder = G_Spawn( );
+ builder->client = 0;
+ VectorCopy( origin, builder->s.pos.trBase );
+ VectorCopy( angles, builder->s.angles );
+ VectorCopy( origin2, builder->s.origin2 );
+ VectorCopy( angles2, builder->s.angles2 );
+ G_SpawnBuildable( builder, buildable );
+}
+
+/*
+============
+G_InstantBuild
+
+This function is extremely similar to the few functions that place a
+buildable on map load. It exists because G_LayoutBuildItem takes a couple
+of frames to finish spawning it, so it's not truly instant
+Do not call this function immediately after the map loads - that's what
+G_LayoutBuildItem is for.
+============
+*/
+gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 )
+{
+ gentity_t *builder, *built;
+ trace_t tr;
+ vec3_t dest;
+
+ builder = G_Spawn( );
+ builder->client = 0;
+ VectorCopy( origin, builder->s.pos.trBase );
+ VectorCopy( angles, builder->s.angles );
+ VectorCopy( origin2, builder->s.origin2 );
+ VectorCopy( angles2, builder->s.angles2 );
+//old method didn't quite work out
+//builder->s.modelindex = buildable;
+//G_FinishSpawningBuildable( builder );
+
+ built = G_Build( builder, buildable, builder->s.pos.trBase, builder->s.angles );
+ G_FreeEntity( builder );
+
+ built->takedamage = qtrue;
+ built->spawned = qtrue; //map entities are already spawned
+ built->health = BG_FindHealthForBuildable( buildable );
+ built->s.generic1 |= B_SPAWNED_TOGGLEBIT;
+
+ // drop towards normal surface
+ VectorScale( built->s.origin2, -4096.0f, dest );
+ VectorAdd( dest, built->s.origin, dest );
+
+ trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask );
+ if( tr.startsolid )
+ {
+ G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n",
+ built->classname, vtos( built->s.origin ) );
+ G_FreeEntity( built );
+ return NULL;
+ }
+
+ //point items in the correct direction
+ VectorCopy( tr.plane.normal, built->s.origin2 );
+
+ // allow to ride movers
+ built->s.groundEntityNum = tr.entityNum;
+
+ G_SetOrigin( built, tr.endpos );
+
+ trap_LinkEntity( built );
+ return built;
+}
+
+/*
+============
+G_SpawnRevertedBuildable
+
+Given a buildhistory, try to replace the lost buildable
+============
+*/
+void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark )
+{
+ vec3_t mins, maxs;
+ int i, j, blockCount, blockers[ MAX_GENTITIES ];
+ gentity_t *targ, *built, *toRecontent[ MAX_GENTITIES ];
+
+ BG_FindBBoxForBuildable( bh->buildable, mins, maxs );
+ VectorAdd( bh->origin, mins, mins );
+ VectorAdd( bh->origin, maxs, maxs );
+ blockCount = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES );
+ for( i = j = 0; i < blockCount; i++ )
+ {
+ targ = g_entities + blockers[ i ];
+ if( targ->s.eType == ET_BUILDABLE )
+ G_FreeEntity( targ );
+ else if( targ->s.eType == ET_PLAYER )
+ {
+ targ->r.contents = 0; // make it intangible
+ toRecontent[ j++ ] = targ; // and remember it
+ }
+ }
+ level.numBuildablesForRemoval = 0;
+ built = G_InstantBuild( bh->buildable, bh->origin, bh->angles, bh->origin2, bh->angles2 );
+ if( built )
+ {
+ built->r.contents = 0;
+ built->think = G_CommitRevertedBuildable;
+ built->nextthink = level.time;
+ built->deconstruct = mark;
+ }
+ for( i = 0; i < j; i++ )
+ toRecontent[ i ]->r.contents = CONTENTS_BODY;
+}
+
+/*
+============
+G_CommitRevertedBuildable
+
+Check if there's anyone occupying me, and if not, become solid and operate as
+normal. Else, try to get rid of them.
+============
+*/
+void G_CommitRevertedBuildable( gentity_t *ent )
+{
+ gentity_t *targ;
+ int i, n, occupants[ MAX_GENTITIES ];
+ vec3_t mins, maxs;
+ int victims = 0;
+
+ VectorAdd( ent->s.origin, ent->r.mins, mins );
+ VectorAdd( ent->s.origin, ent->r.maxs, maxs );
+ trap_UnlinkEntity( ent );
+ n = trap_EntitiesInBox( mins, maxs, occupants, MAX_GENTITIES );
+ trap_LinkEntity( ent );
+
+ for( i = 0; i < n; i++ )
+ {
+ vec3_t gtfo;
+ targ = g_entities + occupants[ i ];
+ if( targ->client )
+ {
+ VectorSet( gtfo, crandom() * 150, crandom() * 150, random() * 150 );
+ VectorAdd( targ->client->ps.velocity, gtfo, targ->client->ps.velocity );
+ victims++;
+ }
+ }
+ if( !victims )
+ { // we're in the clear!
+ ent->r.contents = MASK_PLAYERSOLID;
+ trap_LinkEntity( ent ); // relink
+ // oh dear, manual think set
+ switch( ent->s.modelindex )
+ {
+ case BA_A_SPAWN:
+ ent->think = ASpawn_Think;
+ break;
+ case BA_A_BARRICADE:
+ case BA_A_BOOSTER:
+ ent->think = ABarricade_Think;
+ break;
+ case BA_A_ACIDTUBE:
+ ent->think = AAcidTube_Think;
+ break;
+ case BA_A_HIVE:
+ ent->think = AHive_Think;
+ break;
+ case BA_A_TRAPPER:
+ ent->think = ATrapper_Think;
+ break;
+ case BA_A_OVERMIND:
+ ent->think = AOvermind_Think;
+ break;
+ case BA_A_HOVEL:
+ ent->think = AHovel_Think;
+ break;
+ case BA_H_SPAWN:
+ ent->think = HSpawn_Think;
+ break;
+ case BA_H_MGTURRET:
+ ent->think = HMGTurret_Think;
+ break;
+ case BA_H_TESLAGEN:
+ ent->think = HTeslaGen_Think;
+ break;
+ case BA_H_ARMOURY:
+ ent->think = HArmoury_Think;
+ break;
+ case BA_H_DCC:
+ ent->think = HDCC_Think;
+ break;
+ case BA_H_MEDISTAT:
+ ent->think = HMedistat_Think;
+ break;
+ case BA_H_REACTOR:
+ ent->think = HReactor_Think;
+ break;
+ case BA_H_REPEATER:
+ ent->think = HRepeater_Think;
+ break;
+ }
+ ent->nextthink = level.time + BG_FindNextThinkForBuildable( ent->s.modelindex );
+ // oh if only everything was that simple
+ return;
+ }
+#define REVERT_THINK_INTERVAL 50
+ ent->nextthink = level.time + REVERT_THINK_INTERVAL;
+}
+
+/*
+============
+G_RevertCanFit
+
+take a bhist and make sure you're not overwriting anything by placing it
+============
+*/
+qboolean G_RevertCanFit( buildHistory_t *bh )
+{
+ int i, num, blockers[ MAX_GENTITIES ];
+ vec3_t mins, maxs;
+ gentity_t *targ;
+ vec3_t dist;
+
+ BG_FindBBoxForBuildable( bh->buildable, mins, maxs );
+ VectorAdd( bh->origin, mins, mins );
+ VectorAdd( bh->origin, maxs, maxs );
+ num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ targ = g_entities + blockers[ i ];
+ if( targ->s.eType == ET_BUILDABLE )
+ {
+ VectorSubtract( bh->origin, targ->s.pos.trBase, dist );
+ if( targ->s.modelindex == bh->buildable && VectorLength( dist ) < 10 && targ->health <= 0 )
+ continue; // it's the same buildable, hasn't blown up yet
+ else
+ return qfalse; // can't get rid of this one
+ }
+ else
+ continue;
+ }
+ return qtrue;
+}
+
+/*
+============
+G_LayoutLoad
+
+load the layout .dat file indicated by level.layout and spawn buildables
+as if a builder was creating them
+============
+*/
+void G_LayoutLoad( void )
+{
+ fileHandle_t f;
+ int len;
+ char *layout;
+ char map[ MAX_QPATH ];
+ int buildable = BA_NONE;
+ vec3_t origin = { 0.0f, 0.0f, 0.0f };
+ vec3_t angles = { 0.0f, 0.0f, 0.0f };
+ vec3_t origin2 = { 0.0f, 0.0f, 0.0f };
+ vec3_t angles2 = { 0.0f, 0.0f, 0.0f };
+ char line[ MAX_STRING_CHARS ];
+ int i = 0;
+
+ if( !level.layout[ 0 ] || !Q_stricmp( level.layout, "*BUILTIN*" ) )
+ return;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ),
+ &f, FS_READ );
+ if( len < 0 )
+ {
+ G_Printf( "ERROR: layout %s could not be opened\n", level.layout );
+ return;
+ }
+ layout = G_Alloc( len + 1 );
+ trap_FS_Read( layout, len, f );
+ *( layout + len ) = '\0';
+ trap_FS_FCloseFile( f );
+ while( *layout )
+ {
+ if( i >= sizeof( line ) - 1 )
+ {
+ G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n",
+ va( "layouts/%s/%s.dat", map, level.layout ), line );
+ return;
+ }
+ line[ i++ ] = *layout;
+ line[ i ] = '\0';
+ if( *layout == '\n' )
+ {
+ i = 0;
+ sscanf( line, "%d %f %f %f %f %f %f %f %f %f %f %f %f\n",
+ &buildable,
+ &origin[ 0 ], &origin[ 1 ], &origin[ 2 ],
+ &angles[ 0 ], &angles[ 1 ], &angles[ 2 ],
+ &origin2[ 0 ], &origin2[ 1 ], &origin2[ 2 ],
+ &angles2[ 0 ], &angles2[ 1 ], &angles2[ 2 ] );
+
+ if( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES )
+ G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 );
+ else
+ G_Printf( S_COLOR_YELLOW "WARNING: bad buildable number (%d) in "
+ " layout. skipping\n", buildable );
+ }
+ layout++;
+ }
+}
+
+void G_BaseSelfDestruct( pTeam_t team )
+{
+ int i;
+ gentity_t *ent;
+
+ for( i = MAX_CLIENTS; i < level.num_entities; i++ )
+ {
+ ent = &level.gentities[ i ];
+ if( ent->health <= 0 )
+ continue;
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+ if( team == PTE_HUMANS && ent->biteam != BIT_HUMANS )
+ continue;
+ if( team == PTE_ALIENS && ent->biteam != BIT_ALIENS )
+ continue;
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ }
+}
+
+ int G_LogBuild( buildHistory_t *new )
+ {
+ new->next = level.buildHistory;
+ level.buildHistory = new;
+ return G_CountBuildLog();
+ }
+
+ int G_CountBuildLog( void )
+ {
+ buildHistory_t *ptr, *mark;
+ int i = 0, overflow;
+ for( ptr = level.buildHistory; ptr; ptr = ptr->next, i++ );
+ if( i > g_buildLogMaxLength.integer )
+ {
+ for( overflow = i - g_buildLogMaxLength.integer; overflow > 0; overflow-- )
+ {
+ ptr = level.buildHistory;
+ while( ptr->next )
+ {
+ if( ptr->next->next )
+ ptr = ptr->next;
+ else
+ {
+ while( ( mark = ptr->next ) )
+ {
+ ptr->next = ptr->next->marked;
+ G_Free( mark );
+ }
+ }
+ }
+ }
+ return g_buildLogMaxLength.integer;
+ }
+ return i;
+ }
+
+ char *G_FindBuildLogName( int id )
+ {
+ buildHistory_t *ptr;
+
+ for( ptr = level.buildHistory; ptr && ptr->ID != id; ptr = ptr->next );
+ if( ptr )
+ {
+ if( ptr->ent )
+ {
+ if( ptr->ent->client )
+ return ptr->ent->client->pers.netname;
+ }
+ else if( ptr->name[ 0 ] )
+ {
+ return ptr->name;
+ }
+ }
+
+ return "<buildlog entry expired>";
+ }
+
+/*
+============
+G_NobuildLoad
+
+load the nobuild markers that were previously saved (if there are any).
+============
+*/
+void G_NobuildLoad( void )
+{
+ fileHandle_t f;
+ int len;
+ char *nobuild;
+ char map[ MAX_QPATH ];
+ vec3_t origin = { 0.0f, 0.0f, 0.0f };
+ char line[ MAX_STRING_CHARS ];
+ int i = 0;
+ gentity_t *nb;
+ float area;
+ float height;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ len = trap_FS_FOpenFile( va( "nobuild/%s.dat", map ),
+ &f, FS_READ );
+ if( len < 0 )
+ {
+ // This isn't needed since nobuild is pretty much optional...
+ //G_Printf( "ERROR: nobuild for %s could not be opened\n", map );
+ return;
+ }
+ nobuild = G_Alloc( len + 1 );
+ trap_FS_Read( nobuild, len, f );
+ *( nobuild + len ) = '\0';
+ trap_FS_FCloseFile( f );
+ while( *nobuild )
+ {
+ if( i >= sizeof( line ) - 1 )
+ {
+ return;
+ }
+
+ line[ i++ ] = *nobuild;
+ line[ i ] = '\0';
+ if( *nobuild == '\n' )
+ {
+ i = 0;
+ sscanf( line, "%f %f %f %f %f\n",
+ &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &area, &height );
+
+ // Make the marker...
+ nb = G_Spawn( );
+ nb->s.modelindex = 0;
+ VectorCopy( origin, nb->s.pos.trBase );
+ VectorCopy( origin, nb->r.currentOrigin );
+ nb->noBuild.isNB = qtrue;
+ nb->noBuild.Area = area;
+ nb->noBuild.Height = height;
+ trap_LinkEntity( nb );
+
+ // Log markers made...
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ if( level.nbMarkers[ i ].Marker != NULL )
+ continue;
+
+ level.nbMarkers[ i ].Marker = nb;
+ VectorCopy( origin, level.nbMarkers[ i ].Origin );
+ SnapVector( level.nbMarkers[ i ].Origin );
+ break;
+ }
+
+ }
+ nobuild++;
+ }
+}
+
+/*
+============
+G_NobuildSave
+Save all currently placed nobuild markers into the "nobuild" folder
+============
+*/
+void G_NobuildSave( void )
+{
+ char map[ MAX_QPATH ];
+ char fileName[ MAX_OSPATH ];
+ fileHandle_t f;
+ int len;
+ int i;
+ gentity_t *ent;
+ char *s;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ if( !map[ 0 ] )
+ {
+ G_Printf( "NobuildSave( ): no map is loaded\n" );
+ return;
+ }
+ Com_sprintf( fileName, sizeof( fileName ), "nobuild/%s.dat", map );
+
+ len = trap_FS_FOpenFile( fileName, &f, FS_WRITE );
+ if( len < 0 )
+ {
+ G_Printf( "nobuildsave: could not open %s\n", fileName );
+ return;
+ }
+
+ G_Printf("nobuildsave: saving nobuild to %s\n", fileName );
+
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ ent = &level.gentities[ i ];
+ if( ent->noBuild.isNB != qtrue )
+ continue;
+
+ s = va( "%f %f %f %f %f\n",
+ ent->r.currentOrigin[ 0 ],
+ ent->r.currentOrigin[ 1 ],
+ ent->r.currentOrigin[ 2 ],
+ ent->noBuild.Area,
+ ent->noBuild.Height );
+ trap_FS_Write( s, strlen( s ), f );
+ }
+ trap_FS_FCloseFile( f );
+}
diff --git a/src/game/g_client.c b/src/game/g_client.c
new file mode 100644
index 0000000..b0751de
--- /dev/null
+++ b/src/game/g_client.c
@@ -0,0 +1,2044 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+// g_client.c -- client functions that don't happen every frame
+
+static vec3_t playerMins = {-15, -15, -24};
+static vec3_t playerMaxs = {15, 15, 32};
+
+/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
+potential spawning position for deathmatch games.
+The first time a player enters the game, they will be at an 'initial' spot.
+Targets will be fired when someone spawns in on them.
+"nobots" will prevent bots from using this spot.
+"nohumans" will prevent non-bots from using this spot.
+*/
+void SP_info_player_deathmatch( gentity_t *ent )
+{
+ int i;
+
+ G_SpawnInt( "nobots", "0", &i);
+
+ if( i )
+ ent->flags |= FL_NO_BOTS;
+
+ G_SpawnInt( "nohumans", "0", &i );
+ if( i )
+ ent->flags |= FL_NO_HUMANS;
+}
+
+/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
+equivelant to info_player_deathmatch
+*/
+void SP_info_player_start( gentity_t *ent )
+{
+ ent->classname = "info_player_deathmatch";
+ SP_info_player_deathmatch( ent );
+}
+
+/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+The intermission will be viewed from this point. Target an info_notnull for the view direction.
+*/
+void SP_info_player_intermission( gentity_t *ent )
+{
+}
+
+/*QUAKED info_alien_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+The intermission will be viewed from this point. Target an info_notnull for the view direction.
+*/
+void SP_info_alien_intermission( gentity_t *ent )
+{
+}
+
+/*QUAKED info_human_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+The intermission will be viewed from this point. Target an info_notnull for the view direction.
+*/
+void SP_info_human_intermission( gentity_t *ent )
+{
+}
+
+/*
+===============
+G_OverflowCredits
+===============
+*/
+void G_OverflowCredits( gclient_t *doner, int credits )
+{
+ int i;
+ int maxCredits;
+ int clientNum;
+
+ if( !g_creditOverflow.integer )
+ return;
+
+ if( doner->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ maxCredits = ALIEN_MAX_KILLS;
+ clientNum = level.lastCreditedAlien;
+ }
+ else if( doner->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ maxCredits = HUMAN_MAX_CREDITS;
+ clientNum = level.lastCreditedHuman;
+ }
+ else
+ {
+ return;
+ }
+
+ if( g_creditOverflow.integer == 1 )
+ {
+ // distribute to everyone on team
+ gentity_t *vic;
+
+ i = 0;
+ while( credits > 0 && i < level.maxclients )
+ {
+ i++;
+ clientNum++;
+ if( clientNum >= level.maxclients )
+ clientNum = 0;
+
+ vic = &g_entities[ clientNum ];
+ if( vic->client->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] ||
+ vic->client->ps.persistant[ PERS_CREDIT ] >= maxCredits )
+ continue;
+
+ if( vic->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ level.lastCreditedAlien = clientNum;
+ else
+ level.lastCreditedHuman = clientNum;
+
+ if( vic->client->ps.persistant[ PERS_CREDIT ] + credits > maxCredits )
+ {
+ credits -= maxCredits - vic->client->ps.persistant[ PERS_CREDIT ];
+ vic->client->ps.persistant[ PERS_CREDIT ] = maxCredits;
+ }
+ else
+ {
+ vic->client->ps.persistant[ PERS_CREDIT ] += credits;
+ return;
+ }
+ }
+ }
+ else if( g_creditOverflow.integer == 2 )
+ {
+ // distribute by team rank
+ gclient_t *cl;
+
+ for( i = 0; i < level.numPlayingClients && credits > 0; i++ )
+ {
+ // get the client list sorted by rank
+ cl = &level.clients[ level.sortedClients[ i ] ];
+ if( cl->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] ||
+ cl->ps.persistant[ PERS_CREDIT ] >= maxCredits )
+ continue;
+
+ if( cl->ps.persistant[ PERS_CREDIT ] + credits > maxCredits )
+ {
+ credits -= maxCredits - cl->ps.persistant[ PERS_CREDIT ];
+ cl->ps.persistant[ PERS_CREDIT ] = maxCredits;
+ }
+ else
+ {
+ cl->ps.persistant[ PERS_CREDIT ] += credits;
+ return;
+ }
+ }
+ }
+}
+
+/*
+===============
+G_AddCreditToClient
+===============
+*/
+void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap )
+{
+ if( !client )
+ return;
+
+ //if we're already at the max and trying to add credit then stop
+ if( cap )
+ {
+ if( client->pers.teamSelection == PTE_ALIENS )
+ {
+ if( client->pers.credit >= ALIEN_MAX_KILLS &&
+ credit > 0 )
+ {
+ G_OverflowCredits( client, credit );
+ return;
+ }
+ }
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ {
+ if( client->pers.credit >= HUMAN_MAX_CREDITS &&
+ credit > 0 )
+ {
+ G_OverflowCredits( client, credit );
+ return;
+ }
+ }
+ }
+
+ client->pers.credit += credit;
+
+ if( cap )
+ {
+ if( client->pers.teamSelection == PTE_ALIENS )
+ {
+ if( client->pers.credit > ALIEN_MAX_KILLS )
+ {
+ G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - ALIEN_MAX_KILLS );
+ client->pers.credit = ALIEN_MAX_KILLS;
+ }
+ }
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ {
+ if( client->pers.credit > HUMAN_MAX_CREDITS )
+ {
+ G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - HUMAN_MAX_CREDITS );
+ client->pers.credit = HUMAN_MAX_CREDITS;
+ }
+ }
+ }
+
+ if( client->pers.credit < 0 )
+ client->pers.credit = 0;
+
+ // keep PERS_CREDIT in sync if not following
+ if( client->sess.spectatorState != SPECTATOR_FOLLOW )
+ client->ps.persistant[ PERS_CREDIT ] = client->pers.credit;
+}
+
+
+/*
+=======================================================================
+
+ G_SelectSpawnPoint
+
+=======================================================================
+*/
+
+/*
+================
+SpotWouldTelefrag
+
+================
+*/
+qboolean SpotWouldTelefrag( gentity_t *spot )
+{
+ int i, num;
+ int touch[ MAX_GENTITIES ];
+ gentity_t *hit;
+ vec3_t mins, maxs;
+
+ VectorAdd( spot->s.origin, playerMins, mins );
+ VectorAdd( spot->s.origin, playerMaxs, maxs );
+ num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
+
+ for( i = 0; i < num; i++ )
+ {
+ hit = &g_entities[ touch[ i ] ];
+ //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
+ if( hit->client )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+================
+G_SelectNearestDeathmatchSpawnPoint
+
+Find the spot that we DON'T want to use
+================
+*/
+#define MAX_SPAWN_POINTS 128
+gentity_t *G_SelectNearestDeathmatchSpawnPoint( vec3_t from )
+{
+ gentity_t *spot;
+ vec3_t delta;
+ float dist, nearestDist;
+ gentity_t *nearestSpot;
+
+ nearestDist = 999999;
+ nearestSpot = NULL;
+ spot = NULL;
+
+ while( (spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
+ {
+ VectorSubtract( spot->s.origin, from, delta );
+ dist = VectorLength( delta );
+
+ if( dist < nearestDist )
+ {
+ nearestDist = dist;
+ nearestSpot = spot;
+ }
+ }
+
+ return nearestSpot;
+}
+
+
+/*
+================
+G_SelectRandomDeathmatchSpawnPoint
+
+go to a random point that doesn't telefrag
+================
+*/
+#define MAX_SPAWN_POINTS 128
+gentity_t *G_SelectRandomDeathmatchSpawnPoint( void )
+{
+ gentity_t *spot;
+ int count;
+ int selection;
+ gentity_t *spots[ MAX_SPAWN_POINTS ];
+
+ count = 0;
+ spot = NULL;
+
+ while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
+ {
+ if( SpotWouldTelefrag( spot ) )
+ continue;
+
+ spots[ count ] = spot;
+ count++;
+ }
+
+ if( !count ) // no spots that won't telefrag
+ return G_Find( NULL, FOFS( classname ), "info_player_deathmatch" );
+
+ selection = rand( ) % count;
+ return spots[ selection ];
+}
+
+
+/*
+===========
+G_SelectRandomFurthestSpawnPoint
+
+Chooses a player start, deathmatch start, etc
+============
+*/
+gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
+{
+ gentity_t *spot;
+ vec3_t delta;
+ float dist;
+ float list_dist[ 64 ];
+ gentity_t *list_spot[ 64 ];
+ int numSpots, rnd, i, j;
+
+ numSpots = 0;
+ spot = NULL;
+
+ while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
+ {
+ if( SpotWouldTelefrag( spot ) )
+ continue;
+
+ VectorSubtract( spot->s.origin, avoidPoint, delta );
+ dist = VectorLength( delta );
+
+ for( i = 0; i < numSpots; i++ )
+ {
+ if( dist > list_dist[ i ] )
+ {
+ if( numSpots >= 64 )
+ numSpots = 64 - 1;
+
+ for( j = numSpots; j > i; j-- )
+ {
+ list_dist[ j ] = list_dist[ j - 1 ];
+ list_spot[ j ] = list_spot[ j - 1 ];
+ }
+
+ list_dist[ i ] = dist;
+ list_spot[ i ] = spot;
+ numSpots++;
+
+ if( numSpots > 64 )
+ numSpots = 64;
+
+ break;
+ }
+ }
+
+ if( i >= numSpots && numSpots < 64 )
+ {
+ list_dist[ numSpots ] = dist;
+ list_spot[ numSpots ] = spot;
+ numSpots++;
+ }
+ }
+
+ if( !numSpots )
+ {
+ spot = G_Find( NULL, FOFS( classname ), "info_player_deathmatch" );
+
+ if( !spot )
+ G_Error( "Couldn't find a spawn point" );
+
+ VectorCopy( spot->s.origin, origin );
+ origin[ 2 ] += 9;
+ VectorCopy( spot->s.angles, angles );
+ return spot;
+ }
+
+ // select a random spot from the spawn points furthest away
+ rnd = random( ) * ( numSpots / 2 );
+
+ VectorCopy( list_spot[ rnd ]->s.origin, origin );
+ origin[ 2 ] += 9;
+ VectorCopy( list_spot[ rnd ]->s.angles, angles );
+
+ return list_spot[ rnd ];
+}
+
+
+/*
+================
+G_SelectAlienSpawnPoint
+
+go to a random point that doesn't telefrag
+================
+*/
+gentity_t *G_SelectAlienSpawnPoint( vec3_t preference )
+{
+ gentity_t *spot;
+ int count;
+ gentity_t *spots[ MAX_SPAWN_POINTS ];
+
+ if( level.numAlienSpawns <= 0 )
+ return NULL;
+
+ count = 0;
+ spot = NULL;
+
+ while( ( spot = G_Find( spot, FOFS( classname ),
+ BG_FindEntityNameForBuildable( BA_A_SPAWN ) ) ) != NULL )
+ {
+ if( !spot->spawned )
+ continue;
+
+ if( spot->health <= 0 )
+ continue;
+
+ if( !spot->s.groundEntityNum )
+ continue;
+
+ if( spot->clientSpawnTime > 0 )
+ continue;
+
+ if( G_CheckSpawnPoint( spot->s.number, spot->s.origin,
+ spot->s.origin2, BA_A_SPAWN, NULL ) != NULL )
+ continue;
+
+ spots[ count ] = spot;
+ count++;
+ }
+
+ if( !count )
+ return NULL;
+
+ return G_ClosestEnt( preference, spots, count );
+}
+
+
+/*
+================
+G_SelectHumanSpawnPoint
+
+go to a random point that doesn't telefrag
+================
+*/
+gentity_t *G_SelectHumanSpawnPoint( vec3_t preference )
+{
+ gentity_t *spot;
+ int count;
+ gentity_t *spots[ MAX_SPAWN_POINTS ];
+
+ if( level.numHumanSpawns <= 0 )
+ return NULL;
+
+ count = 0;
+ spot = NULL;
+
+ while( ( spot = G_Find( spot, FOFS( classname ),
+ BG_FindEntityNameForBuildable( BA_H_SPAWN ) ) ) != NULL )
+ {
+ if( !spot->spawned )
+ continue;
+
+ if( spot->health <= 0 )
+ continue;
+
+ if( !spot->s.groundEntityNum )
+ continue;
+
+ if( spot->clientSpawnTime > 0 )
+ continue;
+
+ if( G_CheckSpawnPoint( spot->s.number, spot->s.origin,
+ spot->s.origin2, BA_H_SPAWN, NULL ) != NULL )
+ continue;
+
+ spots[ count ] = spot;
+ count++;
+ }
+
+ if( !count )
+ return NULL;
+
+ return G_ClosestEnt( preference, spots, count );
+}
+
+
+/*
+===========
+G_SelectSpawnPoint
+
+Chooses a player start, deathmatch start, etc
+============
+*/
+gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
+{
+ return G_SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles );
+}
+
+
+/*
+===========
+G_SelectTremulousSpawnPoint
+
+Chooses a player start, deathmatch start, etc
+============
+*/
+gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles )
+{
+ gentity_t *spot = NULL;
+
+ if( team == PTE_ALIENS )
+ spot = G_SelectAlienSpawnPoint( preference );
+ else if( team == PTE_HUMANS )
+ spot = G_SelectHumanSpawnPoint( preference );
+
+ //no available spots
+ if( !spot )
+ return NULL;
+
+ if( team == PTE_ALIENS )
+ G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin );
+ else if( team == PTE_HUMANS )
+ G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin );
+
+ VectorCopy( spot->s.angles, angles );
+ angles[ ROLL ] = 0;
+
+ return spot;
+
+}
+
+
+/*
+===========
+G_SelectInitialSpawnPoint
+
+Try to find a spawn point marked 'initial', otherwise
+use normal spawn selection.
+============
+*/
+gentity_t *G_SelectInitialSpawnPoint( vec3_t origin, vec3_t angles )
+{
+ gentity_t *spot;
+
+ spot = NULL;
+ while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
+ {
+ if( spot->spawnflags & 1 )
+ break;
+ }
+
+ if( !spot || SpotWouldTelefrag( spot ) )
+ {
+ return G_SelectSpawnPoint( vec3_origin, origin, angles );
+ }
+
+ VectorCopy( spot->s.origin, origin );
+ origin[ 2 ] += 9;
+ VectorCopy( spot->s.angles, angles );
+
+ return spot;
+}
+
+/*
+===========
+G_SelectSpectatorSpawnPoint
+
+============
+*/
+gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles )
+{
+ FindIntermissionPoint( );
+
+ VectorCopy( level.intermission_origin, origin );
+ VectorCopy( level.intermission_angle, angles );
+
+ return NULL;
+}
+
+
+/*
+===========
+G_SelectAlienLockSpawnPoint
+
+Try to find a spawn point for alien intermission otherwise
+use normal intermission spawn.
+============
+*/
+gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles )
+{
+ gentity_t *spot;
+
+ spot = NULL;
+ spot = G_Find( spot, FOFS( classname ), "info_alien_intermission" );
+
+ if( !spot )
+ return G_SelectSpectatorSpawnPoint( origin, angles );
+
+ VectorCopy( spot->s.origin, origin );
+ VectorCopy( spot->s.angles, angles );
+
+ return spot;
+}
+
+
+/*
+===========
+G_SelectHumanLockSpawnPoint
+
+Try to find a spawn point for human intermission otherwise
+use normal intermission spawn.
+============
+*/
+gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles )
+{
+ gentity_t *spot;
+
+ spot = NULL;
+ spot = G_Find( spot, FOFS( classname ), "info_human_intermission" );
+
+ if( !spot )
+ return G_SelectSpectatorSpawnPoint( origin, angles );
+
+ VectorCopy( spot->s.origin, origin );
+ VectorCopy( spot->s.angles, angles );
+
+ return spot;
+}
+
+
+/*
+=======================================================================
+
+BODYQUE
+
+=======================================================================
+*/
+
+
+/*
+=============
+BodySink
+
+After sitting around for five seconds, fall into the ground and dissapear
+=============
+*/
+void BodySink( gentity_t *ent )
+{
+ //run on first BodySink call
+ if( !ent->active )
+ {
+ ent->active = qtrue;
+
+ //sinking bodies can't be infested
+ ent->killedBy = ent->s.misc = MAX_CLIENTS;
+ ent->timestamp = level.time;
+ }
+
+ if( level.time - ent->timestamp > 6500 )
+ {
+ G_FreeEntity( ent );
+ return;
+ }
+
+ ent->nextthink = level.time + 100;
+ ent->s.pos.trBase[ 2 ] -= 1;
+}
+
+
+/*
+=============
+BodyFree
+
+After sitting around for a while the body becomes a freebie
+=============
+*/
+void BodyFree( gentity_t *ent )
+{
+ ent->killedBy = -1;
+
+ //if not claimed in the next minute destroy
+ ent->think = BodySink;
+ ent->nextthink = level.time + 60000;
+}
+
+
+/*
+=============
+SpawnCorpse
+
+A player is respawning, so make an entity that looks
+just like the existing corpse to leave behind.
+=============
+*/
+void SpawnCorpse( gentity_t *ent )
+{
+ gentity_t *body;
+ int contents;
+ vec3_t origin, dest;
+ trace_t tr;
+ float vDiff;
+
+ // prevent crashing everyone with bad corpsenum bug
+ if( ent->client->pers.connected != CON_CONNECTED )
+ return;
+
+ VectorCopy( ent->r.currentOrigin, origin );
+
+ trap_UnlinkEntity( ent );
+
+ // if client is in a nodrop area, don't leave the body
+ contents = trap_PointContents( origin, -1 );
+ if( contents & CONTENTS_NODROP )
+ return;
+
+ body = G_Spawn( );
+
+ VectorCopy( ent->s.apos.trBase, body->s.angles );
+ body->s.eFlags = EF_DEAD;
+ body->s.eType = ET_CORPSE;
+ body->s.number = body - g_entities;
+ body->timestamp = level.time;
+ body->s.event = 0;
+ body->r.contents = CONTENTS_CORPSE;
+ body->s.clientNum = ent->client->ps.stats[ STAT_PCLASS ];
+ body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL;
+
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ body->classname = "humanCorpse";
+ else
+ body->classname = "alienCorpse";
+
+ body->s.misc = MAX_CLIENTS;
+
+ body->think = BodySink;
+ body->nextthink = level.time + 20000;
+
+ body->s.legsAnim = ent->s.legsAnim;
+
+ if( !body->nonSegModel )
+ {
+ switch( body->s.legsAnim & ~ANIM_TOGGLEBIT )
+ {
+ case BOTH_DEATH1:
+ case BOTH_DEAD1:
+ body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1;
+ break;
+ case BOTH_DEATH2:
+ case BOTH_DEAD2:
+ body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2;
+ break;
+ case BOTH_DEATH3:
+ case BOTH_DEAD3:
+ default:
+ body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3;
+ break;
+ }
+ }
+ else
+ {
+ switch( body->s.legsAnim & ~ANIM_TOGGLEBIT )
+ {
+ case NSPA_DEATH1:
+ case NSPA_DEAD1:
+ body->s.legsAnim = NSPA_DEAD1;
+ break;
+ case NSPA_DEATH2:
+ case NSPA_DEAD2:
+ body->s.legsAnim = NSPA_DEAD2;
+ break;
+ case NSPA_DEATH3:
+ case NSPA_DEAD3:
+ default:
+ body->s.legsAnim = NSPA_DEAD3;
+ break;
+ }
+ }
+
+ body->takedamage = qfalse;
+
+ body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ];
+ ent->health = 0;
+
+ //change body dimensions
+ BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs );
+ vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ];
+
+ //drop down to match the *model* origins of ent and body
+ VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff );
+ trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask );
+ VectorCopy( tr.endpos, origin );
+
+ G_SetOrigin( body, origin );
+ VectorCopy( origin, body->s.origin );
+ body->s.pos.trType = TR_GRAVITY;
+ body->s.pos.trTime = level.time;
+ VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
+
+ VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
+ trap_LinkEntity( body );
+}
+
+//======================================================================
+
+
+/*
+==================
+G_SetClientViewAngle
+
+==================
+*/
+void G_SetClientViewAngle( gentity_t *ent, vec3_t angle )
+{
+ int i;
+
+ // set the delta angle
+ for( i = 0; i < 3; i++ )
+ {
+ int cmdAngle;
+
+ cmdAngle = ANGLE2SHORT( angle[ i ] );
+ ent->client->ps.delta_angles[ i ] = cmdAngle - ent->client->pers.cmd.angles[ i ];
+ }
+
+ VectorCopy( angle, ent->s.angles );
+ VectorCopy( ent->s.angles, ent->client->ps.viewangles );
+}
+
+/*
+================
+respawn
+================
+*/
+void respawn( gentity_t *ent )
+{
+ SpawnCorpse( ent );
+
+ //TA: Clients can't respawn - they must go thru the class cmd
+ ent->client->pers.classSelection = PCL_NONE;
+ ClientSpawn( ent, NULL, NULL, NULL );
+}
+
+/*
+================
+TeamCount
+
+Returns number of players on a team
+================
+*/
+team_t TeamCount( int ignoreClientNum, int team )
+{
+ int i;
+ int count = 0;
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ if( i == ignoreClientNum )
+ continue;
+
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( level.clients[ i ].sess.sessionTeam == team )
+ count++;
+ }
+
+ return count;
+}
+
+
+/*
+===========
+ClientCleanName
+============
+*/
+static void ClientCleanName( const char *in, char *out, int outSize, qboolean special )
+{
+ int len, colorlessLen;
+ char ch;
+ char *p;
+ int spaces;
+ qboolean invalid = qfalse;
+
+ //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;
+
+ // don't allow nonprinting characters or (dead) console keys
+ if( ch < ' ' || ch > '}' || ch == '`' || ch == '%' )
+ continue;
+
+ // check colors
+ if( Q_IsColorString( in - 1 ) )
+ {
+ // make sure room in dest for both chars
+ if( len > outSize - 2 )
+ break;
+
+ *out++ = ch;
+ len += 2;
+
+ // solo trailing carat is not a color prefix
+ if( !*in ) {
+ *out++ = COLOR_WHITE;
+ break;
+ }
+
+ // don't allow black in a name, unless if special
+ if( ColorIndex( *in ) == 0 && !special )
+ *out++ = COLOR_WHITE;
+ else
+ *out++ = *in;
+
+ in++;
+ 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 names beginning with "[skipnotify]" because it messes up /ignore-related code
+ if( !Q_strncmp( p, "[skipnotify]", 12 ) )
+ invalid = qtrue;
+
+ // don't allow comment-beginning strings because it messes up various parsers
+ if( strstr( p, "//" ) || strstr( p, "/*" ) )
+ invalid = qtrue;
+
+ // don't allow empty names
+ if( *p == 0 || colorlessLen == 0 )
+ invalid = qtrue;
+
+ // if something made the name bad, put them back to UnnamedPlayer
+ if( invalid )
+ Q_strncpyz( p, "UnnamedPlayer", outSize );
+}
+
+
+/*
+===================
+G_NextNewbieName
+
+Generate a unique, known-good name for an UnnamedPlayer
+===================
+*/
+char *G_NextNewbieName( gentity_t *ent )
+{
+ char newname[ MAX_NAME_LENGTH ];
+ char namePrefix[ MAX_NAME_LENGTH - 4 ];
+ char err[ MAX_STRING_CHARS ];
+
+ if( g_newbieNamePrefix.string[ 0 ] )
+ Q_strncpyz( namePrefix, g_newbieNamePrefix.string , sizeof( namePrefix ) );
+ else
+ strcpy( namePrefix, "Newbie#" );
+
+ while( level.numNewbies < 10000 )
+ {
+ strcpy( newname, va( "%s%i", namePrefix, level.numNewbies ) );
+ if ( G_admin_name_check( ent, newname, err, sizeof( err ) ) )
+ {
+ return va( "%s", newname );
+ }
+ level.numNewbies++; // Only increments if the last requested name was used.
+ }
+ return "UnnamedPlayer";
+}
+
+
+/*
+======================
+G_NonSegModel
+
+Reads an animation.cfg to check for nonsegmentation
+======================
+*/
+static qboolean G_NonSegModel( const char *filename )
+{
+ char *text_p;
+ int len;
+ char *token;
+ char text[ 20000 ];
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( !f )
+ {
+ G_Printf( "File not found: %s\n", filename );
+ return qfalse;
+ }
+
+ if( len < 0 )
+ return qfalse;
+
+ if( len == 0 || len >= sizeof( text ) - 1 )
+ {
+ trap_FS_FCloseFile( f );
+ G_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ //EOF
+ if( !token[ 0 ] )
+ break;
+
+ if( !Q_stricmp( token, "nonsegmented" ) )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+===========
+ClientUserInfoChanged
+
+Called from ClientConnect when the player first connects and
+directly by the server system when the player updates a userinfo variable.
+
+The game can override any of the settings and call trap_SetUserinfo
+if desired.
+============
+*/
+void ClientUserinfoChanged( int clientNum, qboolean forceName )
+{
+ gentity_t *ent;
+ int teamTask, teamLeader, health;
+ char *s;
+ char model[ MAX_QPATH ];
+ char buffer[ MAX_QPATH ];
+ char filename[ MAX_QPATH ];
+ char oldname[ MAX_NAME_LENGTH ];
+ char newname[ MAX_NAME_LENGTH ];
+ char err[ MAX_STRING_CHARS ];
+ qboolean revertName = qfalse;
+ qboolean showRenameMsg = qtrue;
+ gclient_t *client;
+ char c1[ MAX_INFO_STRING ];
+ char c2[ MAX_INFO_STRING ];
+ char userinfo[ MAX_INFO_STRING ];
+ pTeam_t team;
+
+ ent = g_entities + clientNum;
+ client = ent->client;
+
+ trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
+
+ // check for malformed or illegal info strings
+ if( !Info_Validate(userinfo) )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "disconnect \"illegal or malformed userinfo\n\"" );
+ trap_DropClient( ent - g_entities,
+ "dropped: illegal or malformed userinfo");
+ }
+
+
+ // check for local client
+ s = Info_ValueForKey( userinfo, "ip" );
+
+ if( !strcmp( s, "localhost" ) )
+ client->pers.localClient = qtrue;
+
+ // check the item prediction
+ s = Info_ValueForKey( userinfo, "cg_predictItems" );
+
+ if( !atoi( s ) )
+ client->pers.predictItemPickup = qfalse;
+ else
+ client->pers.predictItemPickup = qtrue;
+
+ // set name
+ Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) );
+ s = Info_ValueForKey( userinfo, "name" );
+
+ if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) )
+ ClientCleanName( s, newname, sizeof( newname ), qfalse );
+ else
+ ClientCleanName( s, newname, sizeof( newname ), qtrue );
+
+ if( strcmp( oldname, newname ) )
+ {
+ if( !strlen( oldname ) && client->pers.connected != CON_CONNECTED )
+ showRenameMsg = qfalse;
+
+ // in case we need to revert and there's no oldname
+ if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) )
+ ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qfalse );
+ else
+ ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qtrue );
+
+ if( g_newbieNumbering.integer )
+ {
+ if( !strcmp( newname, "UnnamedPlayer" ) )
+ Q_strncpyz( newname, G_NextNewbieName( ent ), sizeof( newname ) );
+ if( !strcmp( oldname, "UnnamedPlayer" ) )
+ Q_strncpyz( oldname, G_NextNewbieName( ent ), sizeof( oldname ) );
+ }
+
+
+ if( !forceName )
+ {
+ if( G_IsMuted( client ) )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "print \"You cannot change your name while you are muted\n\"" );
+ revertName = qtrue;
+ }
+ else if( client->pers.nameChangeTime &&
+ ( level.time - client->pers.nameChangeTime )
+ <= ( g_minNameChangePeriod.value * 1000 ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va(
+ "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"",
+ g_minNameChangePeriod.integer ) );
+ revertName = qtrue;
+ }
+ else if( g_maxNameChanges.integer > 0
+ && client->pers.nameChanges >= g_maxNameChanges.integer
+ && !G_admin_permission( ent, ADMF_SPECIAL ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va(
+ "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"",
+ g_maxNameChanges.integer ) );
+ revertName = qtrue;
+ }
+ }
+
+ if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) );
+ revertName = qtrue;
+ }
+
+ if( revertName )
+ {
+ Q_strncpyz( client->pers.netname, oldname,
+ sizeof( client->pers.netname ) );
+ Info_SetValueForKey( userinfo, "name", oldname );
+ trap_SetUserinfo( clientNum, userinfo );
+ }
+ else
+ {
+ Q_strncpyz( client->pers.netname, newname,
+ sizeof( client->pers.netname ) );
+ Info_SetValueForKey( userinfo, "name", newname );
+ trap_SetUserinfo( clientNum, userinfo );
+ if( client->pers.connected == CON_CONNECTED )
+ {
+ client->pers.nameChangeTime = level.time;
+ client->pers.nameChanges++;
+ }
+ }
+ }
+
+ if( client->sess.sessionTeam == TEAM_SPECTATOR )
+ {
+ if( client->sess.spectatorState == SPECTATOR_SCOREBOARD )
+ Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) );
+ }
+
+ if( client->pers.connected >= CON_CONNECTING && showRenameMsg )
+ {
+ if( strcmp( oldname, client->pers.netname ) )
+ {
+ //dont show if players invisible
+ if( client->sess.invisible != qtrue )
+ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE
+ " renamed to %s^7\n\"", oldname, client->pers.netname ) );
+ if( g_decolourLogfiles.integer)
+ {
+ char decoloured[ MAX_STRING_CHARS ] = "";
+ if( g_decolourLogfiles.integer == 1 )
+ {
+ Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\" -> \"%s^7\")", oldname, client->pers.netname );
+ G_DecolorString( decoloured, decoloured );
+ G_LogPrintfColoured( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum,
+ client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured );
+ }
+ else
+ {
+ G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum,
+ client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured );
+ }
+
+ }
+ else
+ {
+ G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"\n", clientNum,
+ client->pers.ip, client->pers.guid, oldname, client->pers.netname );
+ }
+ G_admin_namelog_update( client, qfalse );
+ }
+ }
+
+ // set max health
+ health = atoi( Info_ValueForKey( userinfo, "handicap" ) );
+ client->pers.maxHealth = health;
+
+ if( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 )
+ client->pers.maxHealth = 100;
+
+ //hack to force a client update if the config string does not change between spawning
+ if( client->pers.classSelection == PCL_NONE )
+ client->pers.maxHealth = 0;
+
+ // set model
+ if( client->ps.stats[ STAT_PCLASS ] == PCL_HUMAN && BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )
+ {
+ Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ),
+ BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) );
+ }
+ else if( client->pers.classSelection == PCL_NONE )
+ {
+ //This looks hacky and frankly it is. The clientInfo string needs to hold different
+ //model details to that of the spawning class or the info change will not be
+ //registered and an axis appears instead of the player model. There is zero chance
+ //the player can spawn with the battlesuit, hence this choice.
+ Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ),
+ BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) );
+ }
+ else
+ {
+ Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( client->pers.classSelection ),
+ BG_FindSkinNameForClass( client->pers.classSelection ) );
+ }
+ Q_strncpyz( model, buffer, sizeof( model ) );
+
+ //don't bother setting model type if spectating
+ if( client->pers.classSelection != PCL_NONE )
+ {
+ //model segmentation
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg",
+ BG_FindModelNameForClass( client->pers.classSelection ) );
+
+ if( G_NonSegModel( filename ) )
+ client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL;
+ else
+ client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL;
+ }
+
+ // wallwalk follow
+ s = Info_ValueForKey( userinfo, "cg_wwFollow" );
+
+ if( atoi( s ) )
+ client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW;
+ else
+ client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW;
+
+ // wallwalk toggle
+ s = Info_ValueForKey( userinfo, "cg_wwToggle" );
+
+ if( atoi( s ) )
+ client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE;
+ else
+ client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE;
+
+ // teamInfo
+ s = Info_ValueForKey( userinfo, "teamoverlay" );
+
+ if( ! *s || atoi( s ) != 0 )
+ client->pers.teamInfo = qtrue;
+ else
+ client->pers.teamInfo = qfalse;
+
+ s = Info_ValueForKey( userinfo, "cg_unlagged" );
+ if( !s[0] || atoi( s ) != 0 )
+ client->pers.useUnlagged = qtrue;
+ else
+ client->pers.useUnlagged = qfalse;
+
+ // team task (0 = none, 1 = offence, 2 = defence)
+ teamTask = atoi( Info_ValueForKey( userinfo, "teamtask" ) );
+ // team Leader (1 = leader, 0 is normal player)
+ teamLeader = client->sess.teamLeader;
+
+ // colors
+ strcpy( c1, Info_ValueForKey( userinfo, "color1" ) );
+ strcpy( c2, Info_ValueForKey( userinfo, "color2" ) );
+
+ team = client->pers.teamSelection;
+
+ // send over a subset of the userinfo keys so other clients can
+ // print scoreboards, display models, and play custom sounds
+ if ( client->sess.invisible != qtrue )
+ {
+ Com_sprintf( userinfo, sizeof( userinfo ),
+ "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\"
+ "hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\"
+ "tl\\%d\\ig\\%16s",
+ client->pers.netname, team, model, model, c1, c2,
+ client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask,
+ teamLeader, BG_ClientListString( &client->sess.ignoreList ) );
+
+ trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo );
+ } else {
+ trap_SetConfigstring( CS_PLAYERS + clientNum, "" );
+ }
+ /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/
+}
+
+
+/*
+===========
+ClientConnect
+
+Called when a player begins connecting to the server.
+Called again for every map change or tournement restart.
+
+The session information will be valid after exit.
+
+Return NULL if the client should be allowed, otherwise return
+a string with the reason for denial.
+
+Otherwise, the client will be sent the current gamestate
+and will eventually get to ClientBegin.
+
+firstTime will be qtrue the very first time a client connects
+to the server machine, but qfalse on map changes and tournement
+restarts.
+============
+*/
+char *ClientConnect( int clientNum, qboolean firstTime )
+{
+ char *value;
+ gclient_t *client;
+ char userinfo[ MAX_INFO_STRING ];
+ gentity_t *ent;
+ char guid[ 33 ];
+ char ip[ 16 ] = {""};
+ char reason[ MAX_STRING_CHARS ] = {""};
+ int i;
+
+ ent = &g_entities[ clientNum ];
+
+ trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
+
+ value = Info_ValueForKey( userinfo, "cl_guid" );
+ Q_strncpyz( guid, value, sizeof( guid ) );
+
+ // check for admin ban
+ if( G_admin_ban_check( userinfo, reason, sizeof( reason ) ) )
+ {
+ return va( "%s", reason );
+ }
+
+ // IP filtering
+ // 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" );
+ i = 0;
+ while( *value && i < sizeof( ip ) - 2 )
+ {
+ if( *value != '.' && ( *value < '0' || *value > '9' ) )
+ break;
+ ip[ i++ ] = *value;
+ value++;
+ }
+ ip[ i ] = '\0';
+ if( G_FilterPacket( value ) )
+ return "You are banned from this server.";
+
+ if( strlen( ip ) < 7 && strcmp( Info_ValueForKey( userinfo, "ip" ), "localhost" ) )
+ {
+ G_AdminsPrintf( "Connect from client with invalid IP: '%s' NAME: '%s^7'\n",
+ ip, Info_ValueForKey( userinfo, "name" ) );
+ return "Invalid client data";
+ }
+
+ // check for a password
+ value = Info_ValueForKey( userinfo, "password" );
+
+ if( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) &&
+ strcmp( g_password.string, value ) != 0 )
+ return "Invalid password";
+
+ // they can connect
+ ent->client = level.clients + clientNum;
+ client = ent->client;
+
+ memset( client, 0, sizeof(*client) );
+
+ // add guid to session so we don't have to keep parsing userinfo everywhere
+ if( !guid[ 0 ] )
+ {
+ Q_strncpyz( client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ sizeof( client->pers.guid ) );
+ }
+ else
+ {
+ Q_strncpyz( client->pers.guid, guid, sizeof( client->pers.guid ) );
+ }
+
+ Q_strncpyz( client->pers.ip, ip, sizeof( client->pers.ip ) );
+ client->pers.adminLevel = G_admin_level( ent );
+
+ // do autoghost now so that there won't be any name conflicts later on
+ if ( g_autoGhost.integer && client->pers.guid[ 0 ] != 'X' )
+ {
+ for ( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if ( i != ent - g_entities && g_entities[i].client && g_entities[i].client->pers.connected != CON_DISCONNECTED && !Q_stricmp( g_entities[i].client->pers.guid, client->pers.guid ) )
+ {
+ trap_SendServerCommand( i, "disconnect \"You may not be connected to this server multiple times\"" );
+ trap_DropClient( i, "disconnected" );
+ }
+ }
+ }
+
+ client->pers.connected = CON_CONNECTING;
+
+ // read or initialize the session data
+ if( firstTime || level.newSession )
+ G_InitSessionData( client, userinfo );
+
+ G_ReadSessionData( client );
+
+ if( firstTime )
+ client->pers.firstConnect = qtrue;
+ else
+ client->pers.firstConnect = qfalse;
+
+ // get and distribute relevent paramters
+ ClientUserinfoChanged( clientNum, qfalse );
+
+ G_admin_set_adminname( ent );
+
+ if( g_decolourLogfiles.integer )
+ {
+ char decoloured[ MAX_STRING_CHARS ] = "";
+ if( g_decolourLogfiles.integer == 1 )
+ {
+ Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\")", client->pers.netname );
+ G_DecolorString( decoloured, decoloured );
+ G_LogPrintfColoured( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum,
+ client->pers.ip, client->pers.guid, client->pers.netname, decoloured );
+ }
+ else
+ {
+ G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum,
+ client->pers.ip, client->pers.guid, client->pers.netname, decoloured );
+ }
+ }
+ else
+ {
+ G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"\n", clientNum,
+ client->pers.ip, client->pers.guid, client->pers.netname );
+ }
+
+ if( client->pers.adminLevel )
+ {
+ G_LogPrintf( "ClientAuth: %i [%s] \"%s^7\" authenticated to admin level %i using GUID %s (^7%s)\n", clientNum, client->pers.ip, client->pers.netname, client->pers.adminLevel, client->pers.guid, client->pers.adminName );
+ }
+
+ // don't do the "xxx connected" messages if they were caried over from previous level
+ if( client->sess.invisible != qtrue )
+ {
+ if( firstTime )
+ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname ) );
+
+ // count current clients and rank for scoreboard
+ CalculateRanks( );
+ G_admin_namelog_update( client, qfalse );
+ }
+
+
+ // if this is after !restart keepteams or !restart switchteams, apply said selection
+ if ( client->sess.restartTeam != PTE_NONE ) {
+ G_ChangeTeam( ent, client->sess.restartTeam );
+ client->sess.restartTeam = PTE_NONE;
+ }
+
+
+ return NULL;
+}
+
+/*
+===========
+ClientBegin
+
+called when a client has finished connecting, and is ready
+to be placed into the level. This will happen every level load,
+and on transition between teams, but doesn't happen on respawns
+============
+*/
+void ClientBegin( int clientNum )
+{
+ gentity_t *ent;
+ gclient_t *client;
+ int flags;
+
+ ent = g_entities + clientNum;
+
+ client = level.clients + clientNum;
+
+ if( ent->r.linked )
+ trap_UnlinkEntity( ent );
+
+ G_InitGentity( ent );
+ ent->touch = 0;
+ ent->pain = 0;
+ ent->client = client;
+
+ client->pers.connected = CON_CONNECTED;
+ client->pers.enterTime = level.time;
+ client->pers.teamState.state = TEAM_BEGIN;
+ client->pers.classSelection = PCL_NONE;
+
+ // save eflags around this, because changing teams will
+ // cause this to happen with a valid entity, and we
+ // want to make sure the teleport bit is set right
+ // so the viewpoint doesn't interpolate through the
+ // world to the new position
+ flags = client->ps.eFlags;
+ memset( &client->ps, 0, sizeof( client->ps ) );
+ memset( &client->pmext, 0, sizeof( client->pmext ) );
+ client->ps.eFlags = flags;
+
+ // locate ent at a spawn point
+
+ ClientSpawn( ent, NULL, NULL, NULL );
+
+ // Ignore invisible players for this section:
+ if ( client->sess.invisible != qtrue )
+ {
+ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) );
+
+ // auto denybuild
+ if( G_admin_permission( ent, ADMF_NO_BUILD ) )
+ client->pers.denyBuild = qtrue;
+
+ // auto mute flag
+ if( G_admin_permission( ent, ADMF_NO_CHAT ) )
+ client->pers.muted = qtrue;
+
+ // name can change between ClientConnect() and ClientBegin()
+ G_admin_namelog_update( client, qfalse );
+
+ // request the clients PTR code
+ trap_SendServerCommand( ent - g_entities, "ptrcrequest" );
+ }
+ G_LogPrintf( "ClientBegin: %i\n", clientNum );
+
+ if( g_clientUpgradeNotice.integer )
+ {
+ if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) )
+ {
+ trap_SendServerCommand( client->ps.clientNum, va( "print \"^3Your client is out of date. Updating your client will allow you to "
+ "become an admin on servers and download maps much more quickly. Please replace your client executable with a newer client. \n\"" ) );
+
+ trap_SendServerCommand( client->ps.clientNum, va("print \"^3Some available clients: \n"
+ "^2TremFusion^7- ^3http://www.tremfusion.net/download/^7\n"
+ "^2FSM-Trem^7 - ^3http://code.google.com/p/fsm-trem/^7\n"
+ "^2MGClient^7 - ^3http://releases.mercenariesguild.net/client/^7\n\"" ) );
+ }
+ }
+
+ // count current clients and rank for scoreboard
+ CalculateRanks( );
+}
+
+/*
+===========
+ClientSpawn
+
+Called every time a client is placed fresh in the world:
+after the first ClientBegin, and after each respawn
+Initializes all non-persistant parts of playerState
+============
+*/
+void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles )
+{
+ int index;
+ vec3_t spawn_origin, spawn_angles;
+ gclient_t *client;
+ int i;
+ clientPersistant_t saved;
+ clientSession_t savedSess;
+ int persistant[ MAX_PERSISTANT ];
+ gentity_t *spawnPoint = NULL;
+ int flags;
+ int savedPing;
+ int teamLocal;
+ int eventSequence;
+ char userinfo[ MAX_INFO_STRING ];
+ vec3_t up = { 0.0f, 0.0f, 1.0f };
+ int maxAmmo, maxClips;
+ weapon_t weapon;
+
+
+ index = ent - g_entities;
+ client = ent->client;
+
+ teamLocal = client->pers.teamSelection;
+
+ //TA: only start client if chosen a class and joined a team
+ if( client->pers.classSelection == PCL_NONE && teamLocal == PTE_NONE )
+ {
+ client->sess.sessionTeam = TEAM_SPECTATOR;
+ client->sess.spectatorState = SPECTATOR_FREE;
+ }
+ else if( client->pers.classSelection == PCL_NONE )
+ {
+ client->sess.sessionTeam = TEAM_SPECTATOR;
+ client->sess.spectatorState = SPECTATOR_LOCKED;
+ }
+
+ //if client is dead and following teammate, stop following before spawning
+ if(ent->client->sess.spectatorClient!=-1)
+ {
+ ent->client->sess.spectatorClient = -1;
+ ent->client->sess.spectatorState = SPECTATOR_FREE;
+ }
+
+ if( origin != NULL )
+ VectorCopy( origin, spawn_origin );
+
+ if( angles != NULL )
+ VectorCopy( angles, spawn_angles );
+
+ // find a spawn point
+ // do it before setting health back up, so farthest
+ // ranging doesn't count this client
+ if( client->sess.sessionTeam == TEAM_SPECTATOR )
+ {
+ if( teamLocal == PTE_NONE )
+ spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles );
+ else if( teamLocal == PTE_ALIENS )
+ spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
+ else if( teamLocal == PTE_HUMANS )
+ spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
+ }
+ else
+ {
+ if( spawn == NULL )
+ {
+ G_Error( "ClientSpawn: spawn is NULL\n" );
+ return;
+ }
+
+ spawnPoint = spawn;
+
+ if( ent != spawn )
+ {
+ //start spawn animation on spawnPoint
+ G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue );
+
+ if( spawnPoint->biteam == PTE_ALIENS )
+ spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME;
+ else if( spawnPoint->biteam == PTE_HUMANS )
+ spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME;
+ }
+ }
+ client->pers.teamState.state = TEAM_ACTIVE;
+
+ // toggle the teleport bit so the client knows to not lerp
+ flags = ent->client->ps.eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED );
+ flags ^= EF_TELEPORT_BIT;
+ G_UnlaggedClear( ent );
+
+ // clear everything but the persistant data
+
+ saved = client->pers;
+ savedSess = client->sess;
+ savedPing = client->ps.ping;
+
+ for( i = 0; i < MAX_PERSISTANT; i++ )
+ persistant[ i ] = client->ps.persistant[ i ];
+
+ eventSequence = client->ps.eventSequence;
+ memset( client, 0, sizeof( *client ) );
+
+ client->pers = saved;
+ client->sess = savedSess;
+ client->ps.ping = savedPing;
+ client->lastkilled_client = -1;
+
+ for( i = 0; i < MAX_PERSISTANT; i++ )
+ client->ps.persistant[ i ] = persistant[ i ];
+
+ client->ps.eventSequence = eventSequence;
+
+ // increment the spawncount so the client will detect the respawn
+ client->ps.persistant[ PERS_SPAWN_COUNT ]++;
+ client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam;
+
+ // restore really persistant things
+ client->ps.persistant[ PERS_SCORE ] = client->pers.score;
+ client->ps.persistant[ PERS_CREDIT ] = client->pers.credit;
+
+ client->airOutTime = level.time + 12000;
+
+ trap_GetUserinfo( index, userinfo, sizeof( userinfo ) );
+ client->ps.eFlags = flags;
+
+ //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection );
+
+ ent->s.groundEntityNum = ENTITYNUM_NONE;
+ ent->client = &level.clients[ index ];
+ ent->takedamage = qtrue;
+ ent->inuse = qtrue;
+ ent->classname = "player";
+ ent->r.contents = CONTENTS_BODY;
+ ent->clipmask = MASK_PLAYERSOLID;
+ ent->die = player_die;
+ ent->waterlevel = 0;
+ ent->watertype = 0;
+ ent->flags = 0;
+
+ //TA: calculate each client's acceleration
+ ent->evaluateAcceleration = qtrue;
+
+ client->ps.stats[ STAT_WEAPONS ] = 0;
+ client->ps.stats[ STAT_WEAPONS2 ] = 0;
+ client->ps.stats[ STAT_SLOTS ] = 0;
+
+ client->ps.eFlags = flags;
+ client->ps.clientNum = index;
+
+ BG_FindBBoxForClass( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL );
+
+ if( client->sess.sessionTeam != TEAM_SPECTATOR )
+ client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] =
+ BG_FindHealthForClass( ent->client->pers.classSelection );
+ else
+ client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = 100;
+
+ // clear entity values
+ if( ent->client->pers.classSelection == PCL_HUMAN )
+ {
+ BG_AddWeaponToInventory( WP_BLASTER, client->ps.stats );
+ BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats );
+ weapon = client->pers.humanItemSelection;
+ }
+ else if( client->sess.sessionTeam != TEAM_SPECTATOR )
+ weapon = BG_FindStartWeaponForClass( ent->client->pers.classSelection );
+ else
+ weapon = WP_NONE;
+
+ BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips );
+ BG_AddWeaponToInventory( weapon, client->ps.stats );
+ client->ps.ammo = maxAmmo;
+ client->ps.clips = maxClips;
+
+ ent->client->ps.stats[ STAT_PCLASS ] = ent->client->pers.classSelection;
+ ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection;
+
+ ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
+ ent->client->ps.stats[ STAT_STATE ] = 0;
+ VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f );
+
+ // health will count down towards max_health
+ ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25;
+
+ //if evolving scale health
+ if( ent == spawn )
+ {
+ ent->health *= ent->client->pers.evolveHealthFraction;
+ client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction;
+ }
+
+ //clear the credits array
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ ent->credits[ i ] = 0;
+
+ client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
+
+ if( mod_jetpackFuel.value >= 10.0f ) {
+ client->jetpackfuel = mod_jetpackFuel.value;
+ }
+
+ G_SetOrigin( ent, spawn_origin );
+ VectorCopy( spawn_origin, client->ps.origin );
+
+#define UP_VEL 150.0f
+#define F_VEL 50.0f
+
+ //give aliens some spawn velocity
+ if( client->sess.sessionTeam != TEAM_SPECTATOR &&
+ client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if( ent == spawn )
+ {
+ //evolution particle system
+ G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) );
+ }
+ else
+ {
+ spawn_angles[ YAW ] += 180.0f;
+ AngleNormalize360( spawn_angles[ YAW ] );
+
+ if( spawnPoint->s.origin2[ 2 ] > 0.0f )
+ {
+ vec3_t forward, dir;
+
+ AngleVectors( spawn_angles, forward, NULL, NULL );
+ VectorScale( forward, F_VEL, forward );
+ VectorAdd( spawnPoint->s.origin2, forward, dir );
+ VectorNormalize( dir );
+
+ VectorScale( dir, UP_VEL, client->ps.velocity );
+ }
+
+ G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 );
+ }
+ }
+ else if( client->sess.sessionTeam != TEAM_SPECTATOR &&
+ client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ spawn_angles[ YAW ] += 180.0f;
+ AngleNormalize360( spawn_angles[ YAW ] );
+ }
+
+ // the respawned flag will be cleared after the attack and jump keys come up
+ client->ps.pm_flags |= PMF_RESPAWNED;
+
+ trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
+ G_SetClientViewAngle( ent, spawn_angles );
+
+ if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) )
+ {
+ /*G_KillBox( ent );*/ //blame this if a newly spawned client gets stuck in another
+ trap_LinkEntity( ent );
+
+ // force the base weapon up
+ client->ps.weapon = WP_NONE;
+ client->ps.weaponstate = WEAPON_READY;
+ }
+
+ // don't allow full run speed for a bit
+ client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
+ client->ps.pm_time = 100;
+
+ client->respawnTime = level.time;
+ client->lastKillTime = level.time;
+
+ client->inactivityTime = level.time + g_inactivity.integer * 1000;
+ client->latched_buttons = 0;
+
+ // set default animations
+ client->ps.torsoAnim = TORSO_STAND;
+ client->ps.legsAnim = LEGS_IDLE;
+
+ if( level.intermissiontime )
+ MoveClientToIntermission( ent );
+ else
+ {
+ // fire the targets of the spawn point
+ if( !spawn )
+ G_UseTargets( spawnPoint, ent );
+
+ // select the highest weapon number available, after any
+ // spawn given items have fired
+ client->ps.weapon = 1;
+
+ for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- )
+ {
+ if( BG_InventoryContainsWeapon( i, client->ps.stats ) )
+ {
+ client->ps.weapon = i;
+ break;
+ }
+ }
+ }
+
+ // run a client frame to drop exactly to the floor,
+ // initialize animations and other things
+ client->ps.commandTime = level.time - 100;
+ ent->client->pers.cmd.serverTime = level.time;
+ ClientThink( ent-g_entities );
+
+ // positively link the client, even if the command times are weird
+ if( client->sess.sessionTeam != TEAM_SPECTATOR )
+ {
+ BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
+ VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
+ trap_LinkEntity( ent );
+ }
+
+ //TA: must do this here so the number of active clients is calculated
+ CalculateRanks( );
+
+ // run the presend to set anything else
+ ClientEndFrame( ent );
+
+ // clear entity state values
+ BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
+}
+
+
+/*
+===========
+ClientDisconnect
+
+Called when a player drops from the server.
+Will not be called between levels.
+
+This should NOT be called directly by any game logic,
+call trap_DropClient(), which will call this and do
+server system housekeeping.
+============
+*/
+void ClientDisconnect( int clientNum )
+{
+ gentity_t *ent;
+ gentity_t *tent;
+ int i;
+ buildHistory_t *ptr;
+
+ ent = g_entities + clientNum;
+
+ if( !ent->client )
+ return;
+
+ // look through the bhist and readjust it if the referenced ent has left
+ for( ptr = level.buildHistory; ptr; ptr = ptr->next )
+ {
+ if( ptr->ent == ent )
+ {
+ ptr->ent = NULL;
+ Q_strncpyz( ptr->name, ent->client->pers.netname, MAX_NETNAME );
+ }
+ }
+
+ if ( ent->client->sess.invisible != qtrue )
+ G_admin_namelog_update( ent->client, qtrue );
+ G_LeaveTeam( ent );
+
+ // stop any following clients
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ // remove any /ignore settings for this clientNum
+ BG_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum );
+ }
+
+ // send effect if they were completely connected
+ if( ent->client->pers.connected == CON_CONNECTED &&
+ ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ {
+ tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
+ tent->s.clientNum = ent->s.clientNum;
+ }
+
+ if( ent->client->pers.connection )
+ ent->client->pers.connection->clientNum = -1;
+
+ G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s\"\n", clientNum,
+ ent->client->pers.ip, ent->client->pers.guid, ent->client->pers.netname );
+
+ trap_UnlinkEntity( ent );
+ ent->s.modelindex = 0;
+ ent->inuse = qfalse;
+ ent->classname = "disconnected";
+ ent->client->pers.connected = CON_DISCONNECTED;
+ ent->client->ps.persistant[ PERS_TEAM ] = TEAM_FREE;
+ ent->client->sess.sessionTeam = TEAM_FREE;
+
+ trap_SetConfigstring( CS_PLAYERS + clientNum, "");
+
+ CalculateRanks( );
+}
diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c
new file mode 100644
index 0000000..cffe9dc
--- /dev/null
+++ b/src/game/g_cmds.c
@@ -0,0 +1,5760 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+/*
+==================
+G_SanitiseString
+
+Remove case and control characters from a player name
+==================
+*/
+void G_SanitiseString( char *in, char *out, int len )
+{
+ qboolean skip = qtrue;
+ int spaces = 0;
+
+ while( *in && len > 0 )
+ {
+ // strip leading white space
+ if( *in == ' ' )
+ {
+ if( skip )
+ {
+ in++;
+ continue;
+ }
+ spaces++;
+ }
+ else
+ {
+ spaces = 0;
+ skip = qfalse;
+ }
+
+ if( Q_IsColorString( in ) )
+ {
+ in += 2; // skip color code
+ continue;
+ }
+
+ if( *in < 32 )
+ {
+ in++;
+ continue;
+ }
+
+ *out++ = tolower( *in++ );
+ len--;
+ }
+ out -= spaces;
+ *out = 0;
+}
+
+/*
+==================
+G_ClientNumberFromString
+
+Returns a player number for either a number or name string
+Returns -1 if invalid
+==================
+*/
+int G_ClientNumberFromString( gentity_t *to, char *s )
+{
+ gclient_t *cl;
+ int idnum;
+ char s2[ MAX_STRING_CHARS ];
+ char n2[ MAX_STRING_CHARS ];
+
+ // numeric values are just slot numbers
+ if( s[ 0 ] >= '0' && s[ 0 ] <= '9' )
+ {
+ idnum = atoi( s );
+
+ if( idnum < 0 || idnum >= level.maxclients )
+ return -1;
+
+ cl = &level.clients[ idnum ];
+
+ if( cl->pers.connected == CON_DISCONNECTED )
+ return -1;
+
+ return idnum;
+ }
+
+ // check for a name match
+ G_SanitiseString( s, s2, sizeof( s2 ) );
+
+ for( idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++ )
+ {
+ if( cl->pers.connected == CON_DISCONNECTED )
+ continue;
+
+ G_SanitiseString( cl->pers.netname, n2, sizeof( n2 ) );
+
+ if( !strcmp( n2, s2 ) )
+ return idnum;
+ }
+
+ return -1;
+}
+
+
+/*
+==================
+G_MatchOnePlayer
+
+This is a companion function to G_ClientNumbersFromString()
+
+returns qtrue if the int array plist only has one client id, false otherwise
+In the case of false, err will be populated with an error message.
+==================
+*/
+qboolean G_MatchOnePlayer( int *plist, char *err, int len )
+{
+ gclient_t *cl;
+ int *p;
+ char line[ MAX_NAME_LENGTH + 10 ] = {""};
+
+ err[ 0 ] = '\0';
+ if( plist[ 0 ] == -1 )
+ {
+ Q_strcat( err, len, "no connected player by that name or slot #" );
+ return qfalse;
+ }
+ if( plist[ 1 ] != -1 )
+ {
+ Q_strcat( err, len, "more than one player name matches. "
+ "be more specific or use the slot #:\n" );
+ for( p = plist; *p != -1; p++ )
+ {
+ cl = &level.clients[ *p ];
+ if( cl->pers.connected == CON_CONNECTED )
+ {
+ Com_sprintf( line, sizeof( line ), "%2i - %s^7\n",
+ *p, cl->pers.netname );
+ if( strlen( err ) + strlen( line ) > len )
+ break;
+ Q_strcat( err, len, line );
+ }
+ }
+ return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+==================
+G_ClientNumbersFromString
+
+Sets plist to an array of integers that represent client numbers that have
+names that are a partial match for s.
+
+Returns number of matching clientids up to MAX_CLIENTS.
+==================
+*/
+int G_ClientNumbersFromString( char *s, int *plist)
+{
+ gclient_t *p;
+ int i, found = 0;
+ char n2[ MAX_NAME_LENGTH ] = {""};
+ char s2[ MAX_NAME_LENGTH ] = {""};
+ int max = MAX_CLIENTS;
+
+ // if a number is provided, it might be a slot #
+ for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ );
+ if( !s[ i ] )
+ {
+ i = atoi( s );
+ if( i >= 0 && i < level.maxclients )
+ {
+ p = &level.clients[ i ];
+ if( p->pers.connected != CON_DISCONNECTED )
+ {
+ *plist = i;
+ return 1;
+ }
+ }
+ // we must assume that if only a number is provided, it is a clientNum
+ *plist = -1;
+ return 0;
+ }
+
+ // now look for name matches
+ G_SanitiseString( s, s2, sizeof( s2 ) );
+ if( strlen( s2 ) < 1 )
+ return 0;
+ for( i = 0; i < level.maxclients && found <= max; i++ )
+ {
+ p = &level.clients[ i ];
+ if( p->pers.connected == CON_DISCONNECTED )
+ {
+ continue;
+ }
+ G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ *plist++ = i;
+ found++;
+ }
+ }
+ *plist = -1;
+ return found;
+}
+
+/*
+==================
+ScoreboardMessage
+
+==================
+*/
+void ScoreboardMessage( gentity_t *ent )
+{
+ char entry[ 1024 ];
+ char string[ 1400 ];
+ int stringlength;
+ int i, j;
+ gclient_t *cl;
+ int numSorted;
+ weapon_t weapon = WP_NONE;
+ upgrade_t upgrade = UP_NONE;
+
+ // send the latest information on all clients
+ string[ 0 ] = 0;
+ stringlength = 0;
+
+ numSorted = level.numConnectedClients;
+
+ for( i = 0; i < numSorted; i++ )
+ {
+ int ping;
+
+ cl = &level.clients[ level.sortedClients[ i ] ];
+
+ if( cl->pers.connected == CON_CONNECTING )
+ ping = -1;
+ else if( cl->sess.spectatorState == SPECTATOR_FOLLOW )
+ ping = cl->pers.ping < 999 ? cl->pers.ping : 999;
+ else
+ ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
+
+ //If (loop) client is a spectator, they have nothing, so indicate such.
+ //Only send the client requesting the scoreboard the weapon/upgrades information for members of their team. If they are not on a team, send it all.
+ if( cl->sess.sessionTeam != TEAM_SPECTATOR &&
+ (ent->client->pers.teamSelection == PTE_NONE || cl->pers.teamSelection == ent->client->pers.teamSelection ) )
+ {
+ weapon = cl->ps.weapon;
+
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) )
+ upgrade = UP_BATTLESUIT;
+ else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) )
+ upgrade = UP_JETPACK;
+ else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) )
+ upgrade = UP_BATTPACK;
+ else if( BG_InventoryContainsUpgrade( UP_HELMET, cl->ps.stats ) )
+ upgrade = UP_HELMET;
+ else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) )
+ upgrade = UP_LIGHTARMOUR;
+ else
+ upgrade = UP_NONE;
+ }
+ else
+ {
+ weapon = WP_NONE;
+ upgrade = UP_NONE;
+ }
+
+ Com_sprintf( entry, sizeof( entry ),
+ " %d %d %d %d %d %d", level.sortedClients[ i ], cl->pers.score, ping,
+ ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade );
+
+ j = strlen( entry );
+
+ if( stringlength + j > 1024 )
+ break;
+
+ strcpy( string + stringlength, entry );
+ stringlength += j;
+ }
+
+ trap_SendServerCommand( ent-g_entities, va( "scores %i %i %i%s", i,
+ level.alienKills, level.humanKills, string ) );
+}
+
+
+/*
+==================
+ConcatArgs
+==================
+*/
+char *ConcatArgs( int start )
+{
+ int i, c, tlen;
+ static char line[ MAX_STRING_CHARS ];
+ int len;
+ char arg[ MAX_STRING_CHARS ];
+
+ len = 0;
+ c = trap_Argc( );
+
+ for( i = start; i < c; i++ )
+ {
+ trap_Argv( i, arg, sizeof( arg ) );
+ tlen = strlen( arg );
+
+ if( len + tlen >= MAX_STRING_CHARS - 1 )
+ break;
+
+ memcpy( line + len, arg, tlen );
+ len += tlen;
+
+ if( len == MAX_STRING_CHARS - 1 )
+ break;
+
+ if( i != c - 1 )
+ {
+ line[ len ] = ' ';
+ len++;
+ }
+ }
+
+ line[ len ] = 0;
+
+ return line;
+}
+
+/*
+==================
+G_Flood_Limited
+
+Determine whether a user is flood limited, and adjust their flood demerits
+==================
+*/
+
+qboolean G_Flood_Limited( gentity_t *ent )
+{
+ int millisSinceLastCommand;
+ int maximumDemerits;
+
+ // This shouldn't be called if g_floodMinTime isn't set, but handle it anyway.
+ if( !g_floodMinTime.integer )
+ return qfalse;
+
+ // Do not limit admins with no censor/flood flag
+ if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) )
+ return qfalse;
+
+ millisSinceLastCommand = level.time - ent->client->pers.lastFloodTime;
+ if( millisSinceLastCommand < g_floodMinTime.integer )
+ ent->client->pers.floodDemerits += ( g_floodMinTime.integer - millisSinceLastCommand );
+ else
+ {
+ ent->client->pers.floodDemerits -= ( millisSinceLastCommand - g_floodMinTime.integer );
+ if( ent->client->pers.floodDemerits < 0 )
+ ent->client->pers.floodDemerits = 0;
+ }
+
+ ent->client->pers.lastFloodTime = level.time;
+
+ // If g_floodMaxDemerits == 0, then we go against g_floodMinTime^2.
+
+ if( !g_floodMaxDemerits.integer )
+ maximumDemerits = g_floodMinTime.integer * g_floodMinTime.integer / 1000;
+ else
+ maximumDemerits = g_floodMaxDemerits.integer;
+
+ if( ent->client->pers.floodDemerits > maximumDemerits )
+ return qtrue;
+
+ return qfalse;
+}
+
+/*
+==================
+Cmd_Give_f
+
+Give items to a client
+==================
+*/
+void Cmd_Give_f( gentity_t *ent )
+{
+ char *name;
+ qboolean give_all = qfalse;
+
+ name = ConcatArgs( 1 );
+ if( Q_stricmp( name, "all" ) == 0 )
+ give_all = qtrue;
+
+ if( give_all || Q_stricmp( name, "health" ) == 0 )
+ {
+ if(!g_devmapNoGod.integer)
+ {
+ ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ];
+ BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats );
+ }
+ }
+
+ if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 )
+ {
+ int credits = give_all ? HUMAN_MAX_CREDITS : atoi( name + 6 );
+ G_AddCreditToClient( ent->client, credits, qtrue );
+ }
+
+ if( give_all || Q_stricmp( name, "stamina" ) == 0 )
+ ent->client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
+
+ if( Q_stricmp( name, "poison" ) == 0 )
+ {
+ ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
+ ent->client->lastBoostedTime = level.time;
+ }
+
+ if( give_all || Q_stricmp( name, "ammo" ) == 0 )
+ {
+ int maxAmmo, maxClips;
+ gclient_t *client = ent->client;
+
+ if( client->ps.weapon != WP_ALEVEL3_UPG &&
+ BG_FindInfinteAmmoForWeapon( client->ps.weapon ) )
+ return;
+
+ BG_FindAmmoForWeapon( client->ps.weapon, &maxAmmo, &maxClips );
+
+ if( BG_FindUsesEnergyForWeapon( client->ps.weapon ) &&
+ BG_InventoryContainsUpgrade( UP_BATTPACK, client->ps.stats ) )
+ maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
+
+ client->ps.ammo = maxAmmo;
+ client->ps.clips = maxClips;
+ }
+}
+
+
+/*
+==================
+Cmd_God_f
+
+Sets client to godmode
+
+argv(0) god
+==================
+*/
+void Cmd_God_f( gentity_t *ent )
+{
+ char *msg;
+
+ if( !g_devmapNoGod.integer )
+ {
+ ent->flags ^= FL_GODMODE;
+
+ if( !( ent->flags & FL_GODMODE ) )
+ msg = "godmode OFF\n";
+ else
+ msg = "godmode ON\n";
+ }
+ else
+ {
+ msg = "Godmode has been disabled.\n";
+ }
+
+ trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) );
+}
+
+
+/*
+==================
+Cmd_Notarget_f
+
+Sets client to notarget
+
+argv(0) notarget
+==================
+*/
+void Cmd_Notarget_f( gentity_t *ent )
+{
+ char *msg;
+
+ if( !g_devmapNoGod.integer )
+ {
+ ent->flags ^= FL_NOTARGET;
+
+ if( !( ent->flags & FL_NOTARGET ) )
+ msg = "notarget OFF\n";
+ else
+ msg = "notarget ON\n";
+ }
+ else
+ {
+ msg = "Godmode has been disabled.\n";
+ }
+
+ trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) );
+}
+
+
+/*
+==================
+Cmd_Noclip_f
+
+argv(0) noclip
+==================
+*/
+void Cmd_Noclip_f( gentity_t *ent )
+{
+ char *msg;
+
+ if( !g_devmapNoGod.integer )
+ {
+ if( ent->client->noclip )
+ msg = "noclip OFF\n";
+ else
+ msg = "noclip ON\n";
+
+ ent->client->noclip = !ent->client->noclip;
+ }
+ else
+ {
+ msg = "Godmode has been disabled.\n";
+ }
+
+ trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) );
+}
+
+
+/*
+==================
+Cmd_LevelShot_f
+
+This is just to help generate the level pictures
+for the menus. It goes to the intermission immediately
+and sends over a command to the client to resize the view,
+hide the scoreboard, and take a special screenshot
+==================
+*/
+void Cmd_LevelShot_f( gentity_t *ent )
+{
+ BeginIntermission( );
+ trap_SendServerCommand( ent - g_entities, "clientLevelShot" );
+}
+
+/*
+=================
+Cmd_Kill_f
+=================
+*/
+void Cmd_Kill_f( gentity_t *ent )
+{
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING )
+ return;
+
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Leave the hovel first (use your destroy key)\n\"" );
+ return;
+ }
+
+ if( g_cheats.integer )
+ {
+ ent->flags &= ~FL_GODMODE;
+ ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0;
+ player_die( ent, ent, ent, 100000, MOD_SUICIDE );
+ }
+ else
+ {
+ if( ent->suicideTime == 0 )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"You will suicide in 20 seconds\n\"" );
+ ent->suicideTime = level.time + 20000;
+ }
+ else if( ent->suicideTime > level.time )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Suicide canceled\n\"" );
+ ent->suicideTime = 0;
+ }
+ }
+}
+
+/*
+==================
+G_LeaveTeam
+==================
+*/
+void G_LeaveTeam( gentity_t *self )
+{
+ pTeam_t team = self->client->pers.teamSelection;
+ gentity_t *ent;
+ int i;
+
+ if( team == PTE_ALIENS )
+ G_RemoveFromSpawnQueue( &level.alienSpawnQueue, self->client->ps.clientNum );
+ else if( team == PTE_HUMANS )
+ G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum );
+ else
+ {
+ if( self->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ {
+ G_StopFollowing( self );
+ }
+ return;
+ }
+
+ // Cancel pending suicides
+ self->suicideTime = 0;
+
+ // stop any following clients
+ G_StopFromFollowing( self );
+
+ for( i = 0; i < level.num_entities; i++ )
+ {
+ ent = &g_entities[ i ];
+ if( !ent->inuse )
+ continue;
+
+ // clean up projectiles
+ if( ent->s.eType == ET_MISSILE && ent->r.ownerNum == self->s.number )
+ G_FreeEntity( ent );
+ if( ent->client && ent->client->pers.connected == CON_CONNECTED )
+ {
+ // cure poison
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
+ ent->client->lastPoisonCloudedClient == self )
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONED &&
+ ent->client->lastPoisonClient == self )
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
+ }
+ }
+}
+
+/*
+=================
+G_ChangeTeam
+=================
+*/
+void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam )
+{
+ pTeam_t oldTeam = ent->client->pers.teamSelection;
+ qboolean isFixingImbalance=qfalse;
+
+ if( oldTeam == newTeam )
+ return;
+
+ G_LeaveTeam( ent );
+ ent->client->pers.teamSelection = newTeam;
+
+ // G_LeaveTeam() calls G_StopFollowing() which sets spec mode to free.
+ // Undo that in this case, or else people can freespec while in the spawn queue on their new team
+ if( newTeam != PTE_NONE )
+ {
+ ent->client->sess.spectatorState = SPECTATOR_LOCKED;
+ }
+
+
+ if ( ( level.numAlienClients - level.numHumanClients > 2 && oldTeam==PTE_ALIENS && newTeam == PTE_HUMANS && level.numHumanSpawns>0 ) ||
+ ( level.numHumanClients - level.numAlienClients > 2 && oldTeam==PTE_HUMANS && newTeam == PTE_ALIENS && level.numAlienSpawns>0 ) )
+ {
+ isFixingImbalance=qtrue;
+ }
+
+ // under certain circumstances, clients can keep their kills and credits
+ // when switching teams
+ if( G_admin_permission( ent, ADMF_TEAMCHANGEFREE ) ||
+ ( g_teamImbalanceWarnings.integer && isFixingImbalance ) ||
+ ( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS )
+ && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) )
+ {
+ if( oldTeam == PTE_ALIENS )
+ ent->client->pers.credit *= (float)FREEKILL_HUMAN / FREEKILL_ALIEN;
+ else if( newTeam == PTE_ALIENS )
+ ent->client->pers.credit *= (float)FREEKILL_ALIEN / FREEKILL_HUMAN;
+ }
+ else
+ {
+ ent->client->pers.credit = 0;
+ ent->client->pers.score = 0;
+ }
+
+ ent->client->ps.persistant[ PERS_KILLED ] = 0;
+ ent->client->pers.statscounters.kills = 0;
+ ent->client->pers.statscounters.structskilled = 0;
+ ent->client->pers.statscounters.assists = 0;
+ ent->client->pers.statscounters.repairspoisons = 0;
+ ent->client->pers.statscounters.headshots = 0;
+ ent->client->pers.statscounters.hits = 0;
+ ent->client->pers.statscounters.hitslocational = 0;
+ ent->client->pers.statscounters.deaths = 0;
+ ent->client->pers.statscounters.feeds = 0;
+ ent->client->pers.statscounters.suicides = 0;
+ ent->client->pers.statscounters.teamkills = 0;
+ ent->client->pers.statscounters.dmgdone = 0;
+ ent->client->pers.statscounters.structdmgdone = 0;
+ ent->client->pers.statscounters.ffdmgdone = 0;
+ ent->client->pers.statscounters.structsbuilt = 0;
+ ent->client->pers.statscounters.timealive = 0;
+ ent->client->pers.statscounters.timeinbase = 0;
+ ent->client->pers.statscounters.dretchbasytime = 0;
+ ent->client->pers.statscounters.jetpackusewallwalkusetime = 0;
+
+ if( G_admin_permission( ent, ADMF_DBUILDER ) )
+ {
+ if( !ent->client->pers.designatedBuilder )
+ {
+ ent->client->pers.designatedBuilder = qtrue;
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Your designation has been restored\n\"" );
+ }
+ }
+ else if( ent->client->pers.designatedBuilder )
+ {
+ ent->client->pers.designatedBuilder = qfalse;
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You have lost designation due to teamchange\n\"" );
+ }
+
+ ent->client->pers.classSelection = PCL_NONE;
+ ClientSpawn( ent, NULL, NULL, NULL );
+
+ ent->client->pers.joinedATeam = qtrue;
+ ent->client->pers.teamChangeTime = level.time;
+
+ //update ClientInfo
+ ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );
+ G_CheckDBProtection( );
+}
+
+/*
+=================
+Cmd_Team_f
+=================
+*/
+void Cmd_Team_f( gentity_t *ent )
+{
+ pTeam_t team;
+ pTeam_t oldteam = ent->client->pers.teamSelection;
+ char s[ MAX_TOKEN_CHARS ];
+ char buf[ MAX_STRING_CHARS ];
+ qboolean force = G_admin_permission(ent, ADMF_FORCETEAMCHANGE);
+ int aliens = level.numAlienClients;
+ int humans = level.numHumanClients;
+
+ // stop team join spam
+ if( level.time - ent->client->pers.teamChangeTime < 1000 )
+ return;
+ // Prevent invisible players from joining a team
+ if ( ent->client->sess.invisible == qtrue )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"You cannot join a team while invisible\n\"" ) );
+ return;
+ }
+
+ if( oldteam == PTE_ALIENS )
+ aliens--;
+ else if( oldteam == PTE_HUMANS )
+ humans--;
+
+ // do warm up
+ if( g_doWarmup.integer && g_warmupMode.integer == 1 &&
+ level.time - level.startTime < g_warmup.integer * 1000 )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"team: you can't join"
+ " a team during warm up (%d seconds remaining)\n\"",
+ g_warmup.integer - ( level.time - level.startTime ) / 1000 ) );
+ return;
+ }
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ if( !strlen( s ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va("print \"team: %i\n\"",
+ oldteam ) );
+ return;
+ }
+
+ if( Q_stricmpn( s, "spec", 4 ) ){
+ if(G_admin_level(ent)<g_minLevelToJoinTeam.integer){
+ trap_SendServerCommand( ent-g_entities,"print \"Sorry, but your admin level is only permitted to spectate.\n\"" );
+ return;
+ }
+ }
+
+ if( !Q_stricmpn( s, "spec", 4 ) )
+ team = PTE_NONE;
+ else if( !force && ent->client->pers.teamSelection == PTE_NONE &&
+ g_maxGameClients.integer && level.numPlayingClients >=
+ g_maxGameClients.integer )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"The maximum number "
+ "of playing clients has been reached (g_maxGameClients = %i)\n\"",
+ g_maxGameClients.integer ) );
+ return;
+ }
+ else if ( ent->client->pers.specExpires > level.time )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't join a team yet. Expires in %d seconds.\n\"",
+ ( ent->client->pers.specExpires - level.time ) / 1000 ) );
+ return;
+ }
+ else if( !Q_stricmpn( s, "alien", 5 ) )
+ {
+ if( g_forceAutoSelect.integer && !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"You can only join teams using autoselect\n\"" );
+ return;
+ }
+
+ if( level.alienTeamLocked && !force )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Alien team has been ^1LOCKED\n\"" ) );
+ return;
+ }
+ else if( level.humanTeamLocked )
+ {
+ // if only one team has been locked, let people join the other
+ // regardless of balance
+ force = qtrue;
+ }
+
+ if( !force && g_teamForceBalance.integer && aliens > humans )
+ {
+ G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL );
+ return;
+ }
+
+
+ team = PTE_ALIENS;
+ }
+ else if( !Q_stricmpn( s, "human", 5 ) )
+ {
+ if( g_forceAutoSelect.integer && !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"You can only join teams using autoselect\n\"" );
+ return;
+ }
+
+ if( level.humanTeamLocked && !force )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Human team has been ^1LOCKED\n\"" ) );
+ return;
+ }
+ else if( level.alienTeamLocked )
+ {
+ // if only one team has been locked, let people join the other
+ // regardless of balance
+ force = qtrue;
+ }
+
+ if( !force && g_teamForceBalance.integer && humans > aliens )
+ {
+ G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL );
+ return;
+ }
+
+ team = PTE_HUMANS;
+ }
+ else if( !Q_stricmp( s, "auto" ) )
+ {
+ if( level.humanTeamLocked && level.alienTeamLocked )
+ team = PTE_NONE;
+ else if( humans > aliens )
+ team = PTE_ALIENS;
+ else if( humans < aliens )
+ team = PTE_HUMANS;
+ else
+ team = PTE_ALIENS + ( rand( ) % 2 );
+
+ if( team == PTE_ALIENS && level.alienTeamLocked )
+ team = PTE_HUMANS;
+ else if( team == PTE_HUMANS && level.humanTeamLocked )
+ team = PTE_ALIENS;
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"Unknown team: %s\n\"", s ) );
+ return;
+ }
+
+ // stop team join spam
+ if( oldteam == team )
+ return;
+
+ //guard against build timer exploit
+ if( oldteam != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
+ ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ||
+ ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ||
+ BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) ||
+ BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"You cannot change teams until build timer expires\n\"" ) );
+ return;
+ }
+
+ if (team != PTE_NONE)
+ {
+ char namebuff[32];
+
+ Q_strncpyz (namebuff, ent->client->pers.netname, sizeof(namebuff));
+ Q_CleanStr (namebuff);
+
+ if (!namebuff[0] || !Q_stricmp (namebuff, "UnnamedPlayer"))
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"Please set your player name before joining a team. Press ESC and use the Options / Game menu or use /name in the console\n\"") );
+ return;
+ }
+ }
+
+
+ G_ChangeTeam( ent, team );
+
+
+
+ if( team == PTE_ALIENS ) {
+ if ( oldteam == PTE_HUMANS )
+ Com_sprintf( buf, sizeof( buf ), "%s^7 abandoned humans and joined the aliens.", ent->client->pers.netname );
+ else
+ Com_sprintf( buf, sizeof( buf ), "%s^7 joined the aliens.", ent->client->pers.netname );
+ }
+ else if( team == PTE_HUMANS ) {
+ if ( oldteam == PTE_ALIENS )
+ Com_sprintf( buf, sizeof( buf ), "%s^7 abandoned the aliens and joined the humans.", ent->client->pers.netname );
+ else
+ Com_sprintf( buf, sizeof( buf ), "%s^7 joined the humans.", ent->client->pers.netname );
+ }
+ else if( team == PTE_NONE ) {
+ if ( oldteam == PTE_HUMANS )
+ Com_sprintf( buf, sizeof( buf ), "%s^7 left the humans.", ent->client->pers.netname );
+ else
+ Com_sprintf( buf, sizeof( buf ), "%s^7 left the aliens.", ent->client->pers.netname );
+ }
+ trap_SendServerCommand( -1, va( "print \"%s\n\"", buf ) );
+ G_LogOnlyPrintf("ClientTeam: %s\n",buf);
+}
+
+
+/*
+==================
+G_Say
+==================
+*/
+static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, const char *prefix )
+{
+ qboolean ignore = qfalse;
+ qboolean specAllChat = qfalse;
+
+ if( !other )
+ return;
+
+ if( !other->inuse )
+ return;
+
+ if( !other->client )
+ return;
+
+ if( other->client->pers.connected != CON_CONNECTED )
+ return;
+
+ if( ( mode == SAY_TEAM || mode == SAY_ACTION_T ) && !OnSameTeam( ent, other ) )
+ {
+ if( other->client->pers.teamSelection != PTE_NONE )
+ return;
+
+ specAllChat = G_admin_permission( other, ADMF_SPEC_ALLCHAT );
+ if( !specAllChat )
+ return;
+
+ // specs with ADMF_SPEC_ALLCHAT flag can see team chat
+ }
+
+ if( mode == SAY_ADMINS &&
+ (!G_admin_permission( other, ADMF_ADMINCHAT ) || other->client->pers.ignoreAdminWarnings ) )
+ return;
+
+ if( mode == SAY_HADMINS &&
+ (!G_admin_permission( other, ADMF_HIGHADMINCHAT ) || other->client->pers.ignoreAdminWarnings ) )
+ return;
+
+ if( BG_ClientListTest( &other->client->sess.ignoreList, ent-g_entities ) )
+ ignore = qtrue;
+
+ if ( ignore && g_fullIgnore.integer )
+ return;
+
+ trap_SendServerCommand( other-g_entities, va( "%s \"%s%s%s%c%c%s\"",
+ ( mode == SAY_TEAM || mode == SAY_ACTION_T ) ? "tchat" : "chat",
+ ( ignore ) ? "[skipnotify]" : "",
+ ( specAllChat ) ? prefix : "",
+ name, Q_COLOR_ESCAPE, color, message ) );
+}
+
+#define EC "\x19"
+
+void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText )
+{
+ int j;
+ gentity_t *other;
+ int color;
+ const char *prefix;
+ char name[ 64 ];
+ // don't let text be too long for malicious reasons
+ char text[ MAX_SAY_TEXT ];
+ char location[ 64 ];
+
+ // Bail if the text is blank.
+ if( ! chatText[0] )
+ return;
+
+ // Invisible players cannot use chat
+ if( ent->client->sess.invisible == qtrue )
+ {
+ if( !G_admin_cmd_check( ent, qtrue ) )
+ trap_SendServerCommand( ent-g_entities, "print \"You cannot chat while invisible\n\"" );
+ return;
+ }
+
+ // Spam limit: If they said this message recently, ignore it.
+ if( g_spamTime.integer )
+ {
+ if ( ( level.time - ent->client->pers.lastMessageTime ) < ( g_spamTime.integer * 1000 ) &&
+ !Q_stricmp( ent->client->pers.lastMessage, chatText) &&
+ !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) &&
+ ent->client->pers.floodDemerits <= g_floodMaxDemerits.integer )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your message has been ignored to prevent spam\n\"" );
+ return;
+ }
+ else
+ {
+ ent->client->pers.lastMessageTime = level.time;
+
+ Q_strncpyz( ent->client->pers.lastMessage, chatText,
+ sizeof( ent->client->pers.lastMessage ) );
+ }
+ }
+
+ // Flood limit. If they're talking too fast, determine that and return.
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
+ return;
+ }
+
+ if (g_chatTeamPrefix.integer && ent && ent->client )
+ {
+ switch( ent->client->pers.teamSelection)
+ {
+ default:
+ case PTE_NONE:
+ prefix = "[^3S^7] ";
+ break;
+
+ case PTE_ALIENS:
+ prefix = "[^1A^7] ";
+ break;
+
+ case PTE_HUMANS:
+ prefix = "[^4H^7] ";
+ }
+ }
+ else
+ prefix = "";
+
+ switch( mode )
+ {
+ default:
+ case SAY_ALL:
+ G_LogPrintf( "say: %s^7: %s^7\n", ent->client->pers.netname, chatText );
+ Com_sprintf( name, sizeof( name ), "%s%s%c%c"EC": ", prefix,
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_GREEN;
+ break;
+
+ case SAY_TEAM:
+ G_LogPrintf( "sayteam: %s%s^7: %s^7\n", prefix, ent->client->pers.netname, chatText );
+ if( Team_GetLocationMsg( ent, location, sizeof( location ) ) )
+ Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC") (%s)"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location );
+ else
+ Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC")"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+
+ if( ent->client->pers.teamSelection == PTE_NONE )
+ color = COLOR_YELLOW;
+ else
+ color = COLOR_CYAN;
+ break;
+
+ case SAY_TELL:
+ if( target && OnSameTeam( target, ent ) &&
+ Team_GetLocationMsg( ent, location, sizeof( location ) ) )
+ Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"] (%s)"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location );
+ else
+ Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"]"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_MAGENTA;
+ break;
+
+ case SAY_ACTION:
+ G_LogPrintf( "action: %s^7: %s^7\n", ent->client->pers.netname, chatText );
+ Com_sprintf( name, sizeof( name ), "^2%s^7%s%s%c%c"EC" ", g_actionPrefix.string, prefix,
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_WHITE;
+ break;
+
+ case SAY_ACTION_T:
+ G_LogPrintf( "actionteam: %s%s^7: %s^7\n", prefix, ent->client->pers.netname, chatText );
+ if( Team_GetLocationMsg( ent, location, sizeof( location ) ) )
+ Com_sprintf( name, sizeof( name ), EC"^5%s^7%s%c%c"EC"(%s)"EC" ", g_actionPrefix.string,
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location );
+ else
+ Com_sprintf( name, sizeof( name ), EC"^5%s^7%s%c%c"EC""EC" ", g_actionPrefix.string,
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_WHITE;
+ break;
+
+ case SAY_ADMINS:
+ if( G_admin_permission( ent, ADMF_ADMINCHAT ) ) //Differentiate between inter-admin chatter and user-admin alerts
+ {
+ G_LogPrintf( "say_admins: [ADMIN]%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText );
+ Com_sprintf( name, sizeof( name ), "%s[ADMIN]%s%c%c"EC": ", prefix,
+ ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_MAGENTA;
+ }
+ else
+ {
+ G_LogPrintf( "say_admins: [PLAYER]%s^7: %s^7\n", ent->client->pers.netname, chatText );
+ Com_sprintf( name, sizeof( name ), "%s[PLAYER]%s%c%c"EC": ", prefix,
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_MAGENTA;
+ }
+ break;
+
+ case SAY_HADMINS:
+ if( G_admin_permission( ent, ADMF_HIGHADMINCHAT ) ) //Differentiate between inter-high-admin chatter and lower-admin-high-admin-admin alerts and user-admin alerts
+ {
+ G_LogPrintf( "say_hadmins: ^5[^1HIGH ADMIN^5]^7%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText );
+ Com_sprintf( name, sizeof( name ), "%s^5[^1HIGH ADMIN^5]^7%s%c%c"EC": ", prefix,
+ ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_RED;
+ }
+ else if( G_admin_permission( ent, ADMF_ADMINCHAT ) )
+ {
+ G_LogPrintf( "say_haadmins: ^1[^6LOWER ADMIN^1]^7%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText );
+ Com_sprintf( name, sizeof( name ), "%s[^6LOWER ADMIN^7]%s%c%c"EC": ", prefix,
+ ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_RED;
+ }
+ else
+ {
+ G_LogPrintf( "say_hadmins: [PLAYER]%s^7: %s^7\n", ent->client->pers.netname, chatText );
+ Com_sprintf( name, sizeof( name ), "%s[PLAYER]%s%c%c"EC": ", prefix,
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_RED;
+ }
+ break;
+}
+
+
+ if( mode!=SAY_TEAM && ent && ent->client && ent->client->pers.teamSelection == PTE_NONE && G_admin_level(ent)<g_minLevelToSpecMM1.integer )
+ {
+ trap_SendServerCommand( ent-g_entities,va( "print \"Sorry, but your admin level may only use teamchat while spectating.\n\"") );
+ return;
+ }
+
+ Com_sprintf( text, sizeof( text ), "%s^7", chatText );
+
+ if( ent && ent->client && g_aimbotAdvertBan.integer && ( Q_stricmp( text, "^1N^7ullify for ^1T^7remulous [beta] | Get it at CheatersUtopia.com^7" ) == 0 ) )
+ {
+ trap_SendConsoleCommand( 0,
+ va( "!ban %s %s %s\n",
+ ent->client->pers.ip,
+ ( Q_stricmp( g_aimbotAdvertBanTime.string, "0" ) == 1 ) ? g_aimbotAdvertBanTime.string : "" ,
+ g_aimbotAdvertBanReason.string ) );
+ Q_strncpyz( text, "^7has been caught hacking and will be dealt with.", sizeof( text ) );
+ }
+
+ if( target )
+ {
+ G_SayTo( ent, target, mode, color, name, text, prefix );
+ return;
+ }
+
+
+
+ // Ugly hax: if adminsayfilter is off, do the SAY first to prevent text from going out of order
+ if( !g_adminSayFilter.integer )
+ {
+ // send it to all the apropriate clients
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ other = &g_entities[ j ];
+ G_SayTo( ent, other, mode, color, name, text, prefix );
+ }
+ }
+
+ if( g_adminParseSay.integer && ( mode== SAY_ALL || mode == SAY_TEAM ) )
+ {
+ if( G_admin_cmd_check ( ent, qtrue ) && g_adminSayFilter.integer )
+ {
+ return;
+ }
+ }
+
+ // if it's on, do it here, where it won't happen if it was an admin command
+ if( g_adminSayFilter.integer )
+ {
+ // send it to all the apropriate clients
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ other = &g_entities[ j ];
+ G_SayTo( ent, other, mode, color, name, text, prefix );
+ }
+ }
+
+
+}
+
+static void Cmd_SayArea_f( gentity_t *ent )
+{
+ int entityList[ MAX_GENTITIES ];
+ int num, i;
+ int color = COLOR_BLUE;
+ const char *prefix;
+ vec3_t range = { HELMET_RANGE, HELMET_RANGE, HELMET_RANGE };
+ vec3_t mins, maxs;
+ char *msg = ConcatArgs( 1 );
+ char name[ 64 ];
+
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
+ return;
+ }
+
+ if (g_chatTeamPrefix.integer)
+ {
+ switch( ent->client->pers.teamSelection)
+ {
+ default:
+ case PTE_NONE:
+ prefix = "[^3S^7] ";
+ break;
+
+ case PTE_ALIENS:
+ prefix = "[^1A^7] ";
+ break;
+
+ case PTE_HUMANS:
+ prefix = "[^4H^7] ";
+ }
+ }
+ else
+ prefix = "";
+
+ G_LogPrintf( "sayarea: %s%s^7: %s\n", prefix, ent->client->pers.netname, msg );
+ Com_sprintf( name, sizeof( name ), EC"<%s%c%c"EC"> ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+
+ VectorAdd( ent->s.origin, range, maxs );
+ VectorSubtract( ent->s.origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_TEAM, color, name, msg, prefix );
+
+ //Send to ADMF_SPEC_ALLCHAT candidates
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( (&g_entities[ i ])->client->pers.teamSelection == PTE_NONE &&
+ G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) )
+ {
+ G_SayTo( ent, &g_entities[ i ], SAY_TEAM, color, name, msg, prefix );
+ }
+ }
+}
+
+
+/*
+==================
+Cmd_Say_f
+==================
+*/
+static void Cmd_Say_f( gentity_t *ent )
+{
+ char *p;
+ char *args;
+ int mode = SAY_ALL;
+ int skipargs = 0;
+
+ args = G_SayConcatArgs( 0 );
+ if( Q_stricmpn( args, "say_team ", 9 ) == 0 )
+ mode = SAY_TEAM;
+ if( Q_stricmpn( args, "say_admins ", 11 ) == 0 || Q_stricmpn( args, "a ", 2 ) == 0)
+ mode = SAY_ADMINS;
+ if( Q_stricmpn( args, "say_hadmins ", 12 ) == 0 || Q_stricmpn( args, "ha ", 3 ) == 0)
+ mode = SAY_HADMINS;
+
+ // support parsing /m out of say text since some people have a hard
+ // time figuring out what the console is.
+ if( !Q_stricmpn( args, "say /m ", 7 ) ||
+ !Q_stricmpn( args, "say_team /m ", 12 ) ||
+ !Q_stricmpn( args, "say /mt ", 8 ) ||
+ !Q_stricmpn( args, "say_team /mt ", 13 ) )
+ {
+ G_PrivateMessage( ent );
+ return;
+ }
+
+ if( !Q_stricmpn( args, "say /a ", 7) ||
+ !Q_stricmpn( args, "say_team /a ", 12) ||
+ !Q_stricmpn( args, "say /say_admins ", 16) ||
+ !Q_stricmpn( args, "say_team /say_admins ", 21) )
+ {
+ mode = SAY_ADMINS;
+ skipargs=1;
+ }
+
+ if( !Q_stricmpn( args, "say /ha ", 8) ||
+ !Q_stricmpn( args, "say_team /ha ", 13) ||
+ !Q_stricmpn( args, "say /say_hadmins ", 17) ||
+ !Q_stricmpn( args, "say_team /say_hadmins ", 22) )
+ {
+ mode = SAY_HADMINS;
+ skipargs=1;
+ }
+
+ if( mode == SAY_ADMINS)
+ if(!G_admin_permission( ent, ADMF_ADMINCHAT ) )
+ {
+ if( !g_publicSayadmins.integer )
+ {
+ ADMP( "Sorry, but public use of say_admins has been disabled.\n" );
+ return;
+ }
+ else
+ {
+ ADMP( "Your message has been sent to any available admins and to the server logs.\n" );
+ }
+ }
+
+ if( mode == SAY_HADMINS)
+ if(!G_admin_permission( ent, ADMF_HIGHADMINCHAT ) )
+ {
+ {
+ ADMP( "You don't have permissions to see/use this channel.\n" );
+ }
+ }
+
+ if(!Q_stricmpn( args, "say /me ", 8 ) )
+ {
+ if( g_actionPrefix.string[0] )
+ {
+ mode = SAY_ACTION;
+ skipargs=1;
+ } else return;
+ }
+ else if(!Q_stricmpn( args, "say_team /me ", 13 ) )
+ {
+ if( g_actionPrefix.string[0] )
+ {
+ mode = SAY_ACTION_T;
+ skipargs=1;
+ } else return;
+ }
+ else if( !Q_stricmpn( args, "me ", 3 ) )
+ {
+ if( g_actionPrefix.string[0] )
+ {
+ mode = SAY_ACTION;
+ } else return;
+ }
+ else if( !Q_stricmpn( args, "me_team ", 8 ) )
+ {
+ if( g_actionPrefix.string[0] )
+ {
+ mode = SAY_ACTION_T;
+ } else return;
+ }
+
+
+ if( g_allowShare.integer )
+ {
+ args = G_SayConcatArgs(0);
+ if( !Q_stricmpn( args, "say /share", 10 ) ||
+ !Q_stricmpn( args, "say_team /share", 15 ) )
+ {
+ Cmd_Share_f( ent );
+ return;
+ }
+ if( !Q_stricmpn( args, "say /donate", 11 ) ||
+ !Q_stricmpn( args, "say_team /donate", 16 ) )
+ {
+ Cmd_Donate_f( ent );
+ return;
+ }
+ }
+
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ p = G_SayConcatArgs( 1 + skipargs );
+
+ G_Say( ent, NULL, mode, p );
+}
+
+/*
+==================
+Cmd_Tell_f
+==================
+*/
+static void Cmd_Tell_f( gentity_t *ent )
+{
+ int targetNum;
+ gentity_t *target;
+ char *p;
+ char arg[MAX_TOKEN_CHARS];
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ trap_Argv( 1, arg, sizeof( arg ) );
+ targetNum = atoi( arg );
+
+ if( targetNum < 0 || targetNum >= level.maxclients )
+ return;
+
+ target = &g_entities[ targetNum ];
+ if( !target || !target->inuse || !target->client )
+ return;
+
+ p = ConcatArgs( 2 );
+
+ G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p );
+ G_Say( ent, target, SAY_TELL, p );
+ // don't tell to the player self if it was already directed to this player
+ // also don't send the chat back to a bot
+ if( ent != target )
+ G_Say( ent, ent, SAY_TELL, p );
+}
+
+/*
+==================
+Cmd_Where_f
+==================
+*/
+void Cmd_Where_f( gentity_t *ent )
+{
+ trap_SendServerCommand( ent-g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) );
+}
+
+
+static qboolean map_is_votable( const char *map )
+{
+ char maps[ MAX_CVAR_VALUE_STRING ];
+ char *token, *token_p;
+
+ if( !g_votableMaps.string[ 0 ] )
+ return qtrue;
+
+ Q_strncpyz( maps, g_votableMaps.string, sizeof( maps ) );
+ token_p = maps;
+ while( *( token = COM_Parse( &token_p ) ) )
+ {
+ if( !Q_stricmp( token, map ) )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+==================
+Cmd_CallVote_f
+==================
+*/
+void Cmd_CallVote_f( gentity_t *ent )
+{
+ int i;
+ char arg1[ MAX_STRING_TOKENS ];
+ char arg2[ MAX_STRING_TOKENS ];
+ int clientNum = -1;
+ char name[ MAX_NETNAME ];
+ char *arg1plus;
+ char *arg2plus;
+ char message[ MAX_STRING_CHARS ];
+ char targetname[ MAX_NAME_LENGTH] = "";
+ char reason[ MAX_STRING_CHARS ] = "";
+ char *ptr = NULL;
+
+ arg1plus = G_SayConcatArgs( 1 );
+ arg2plus = G_SayConcatArgs( 2 );
+
+ // Invisible players cannot call votes
+ if( ent->client->sess.invisible == qtrue )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"You cannot call votes while invisible\n\"" );
+ return;
+ }
+
+ if( !g_allowVote.integer )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" );
+ return;
+ }
+
+ // Flood limit. If they're talking too fast, determine that and return.
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your /callvote attempt is flood-limited; wait before chatting again\n\"" );
+ return;
+ }
+
+ //see if they can vote
+ if( G_admin_permission( ent, ADMF_NO_VOTE ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" );
+ return;
+ }
+
+ if( g_voteMinTime.integer
+ && ent->client->pers.firstConnect
+ && level.time - ent->client->pers.enterTime < g_voteMinTime.integer * 1000
+ && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )
+ && (level.numPlayingClients > 0 && level.numConnectedClients>1) )
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"You must wait %d seconds after connecting before calling a vote\n\"",
+ g_voteMinTime.integer ) );
+ return;
+ }
+
+ if( level.voteTime )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress\n\"" );
+ return;
+ }
+
+ if( g_voteLimit.integer > 0
+ && ent->client->pers.voteCount >= g_voteLimit.integer
+ && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"You have already called the maximum number of votes (%d)\n\"",
+ g_voteLimit.integer ) );
+ return;
+ }
+
+ if( G_IsMuted( ent->client ) )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "print \"You are muted and cannot call votes\n\"" );
+ return;
+ }
+
+ // make sure it is a valid command to vote on
+ trap_Argv( 1, arg1, sizeof( arg1 ) );
+ trap_Argv( 2, arg2, sizeof( arg2 ) );
+
+ if( strchr( arg1plus, ';' ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" );
+ return;
+ }
+
+ // if there is still a vote to be executed
+ if( level.voteExecuteTime )
+ {
+ if( !Q_stricmp( level.voteString, "map_restart" ) )
+ {
+ G_admin_maplog_result( "r" );
+ }
+ else if( !Q_stricmpn( level.voteString, "map", 3 ) )
+ {
+ G_admin_maplog_result( "m" );
+ }
+
+ level.voteExecuteTime = 0;
+ trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) );
+ }
+
+ level.votePassThreshold=50;
+
+ ptr = strstr(arg1plus, " -");
+ if( ptr )
+ {
+ *ptr = '\0';
+ ptr+=2;
+
+ if( *ptr == 'r' || *ptr=='R' )
+ {
+ ptr++;
+ while( *ptr == ' ' )
+ ptr++;
+ strcpy(reason, ptr);
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"callvote: Warning: invalid argument specified \n\"" );
+ }
+ }
+
+ // detect clientNum for partial name match votes
+ if( !Q_stricmp( arg1, "kick" ) ||
+ !Q_stricmp( arg1, "spec" ) ||
+ !Q_stricmp( arg1, "mute" ) ||
+ !Q_stricmp( arg1, "unmute" ) )
+ {
+ int clientNums[ MAX_CLIENTS ] = { -1 };
+ int numMatches=0;
+ char err[ MAX_STRING_CHARS ] = "";
+
+ Q_strncpyz(targetname, arg2plus, sizeof(targetname));
+ ptr = strstr(targetname, " -");
+ if( ptr )
+ *ptr = '\0';
+
+ if( g_requireVoteReasons.integer && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) && !Q_stricmp( arg1, "kick" ) && reason[ 0 ]=='\0' )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"callvote: You must specify a reason. Use /callvote kick [player] -r [reason] \n\"" );
+ return;
+ }
+
+ if( !targetname[ 0 ] )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callvote: no target\n\"" );
+ return;
+ }
+
+ numMatches = G_ClientNumbersFromString( targetname, clientNums );
+ if( numMatches == 1 )
+ {
+ // there was only one partial name match
+ clientNum = clientNums[ 0 ];
+ }
+ else
+ {
+ // look for an exact name match (sets clientNum to -1 if it fails)
+ clientNum = G_ClientNumberFromString( ent, targetname );
+ }
+
+ if( clientNum==-1 && numMatches > 1 )
+ {
+ G_MatchOnePlayer( clientNums, err, sizeof( err ) );
+ ADMP( va( "^3callvote: ^7%s\n", err ) );
+ return;
+ }
+
+ if( clientNum != -1 &&
+ level.clients[ clientNum ].pers.connected != CON_CONNECTED )
+ {
+ clientNum = -1;
+ }
+
+ if( clientNum != -1 )
+ {
+ Q_strncpyz( name, level.clients[ clientNum ].pers.netname,
+ sizeof( name ) );
+ Q_CleanStr( name );
+ if ( G_admin_permission ( &g_entities[ clientNum ], ADMF_IMMUNITY ) )
+ {
+ char reasonprint[ MAX_STRING_CHARS ] = "";
+
+ if( reason[ 0 ] != '\0' )
+ Com_sprintf(reasonprint, sizeof(reasonprint), "With reason: %s", reason);
+
+ Com_sprintf( message, sizeof( message ), "%s^7 attempted /callvote %s %s on immune admin %s^7 %s^7",
+ ent->client->pers.netname, arg1, targetname, g_entities[ clientNum ].client->pers.netname, reasonprint );
+ }
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callvote: invalid player\n\"" );
+ return;
+ }
+ }
+
+ if( !Q_stricmp( arg1, "kick" ) )
+ {
+ if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callvote: admin is immune from vote kick\n\"" );
+ G_AdminsPrintf("%s\n",message);
+ G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse );
+ return;
+ }
+
+ // use ip in case this player disconnects before the vote ends
+ Com_sprintf( level.voteString, sizeof( level.voteString ),
+ "!ban %s \"%s\" vote kick", level.clients[ clientNum ].pers.ip,
+ g_adminTempBan.string );
+ if ( reason[0]!='\0' )
+ Q_strcat( level.voteString, sizeof( level.voteDisplayString ), va( "(%s^7)", reason ) );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ),
+ "Kick player \'%s\'", name );
+ }
+ else if( !Q_stricmp( arg1, "spec" ) )
+ {
+ if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"callvote: admin is immune from vote spec\n\"" );
+ return;
+ }
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "!putteam %i s %s", clientNum, g_adminTempSpec.string );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Spec player \'%s\'", name );
+
+ }
+ else if( !Q_stricmp( arg1, "mute" ) )
+ {
+ if( G_IsMuted( &level.clients[ clientNum ] ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callvote: player is already muted\n\"" );
+ return;
+ }
+
+ if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callvote: admin is immune from vote mute\n\"" );
+ G_AdminsPrintf("%s\n",message);
+ G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse );
+ return;
+ }
+ Com_sprintf( level.voteString, sizeof( level.voteString ),
+ "!mute %i %s", clientNum, g_adminTempMute.string );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ),
+ "Mute player \'%s\'", name );
+ }
+ else if( !Q_stricmp( arg1, "unmute" ) )
+ {
+ if( !G_IsMuted( &level.clients[ clientNum ] ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callvote: player is not currently muted\n\"" );
+ return;
+ }
+ Com_sprintf( level.voteString, sizeof( level.voteString ),
+ "!unmute %i", clientNum );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ),
+ "Un-Mute player \'%s\'", name );
+ }
+ else if( !Q_stricmp( arg1, "map_restart" ) )
+ {
+ if( g_mapvoteMaxTime.integer
+ && (( level.time - level.startTime ) >= g_mapvoteMaxTime.integer * 1000 )
+ && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )
+ && (level.numPlayingClients > 0 && level.numConnectedClients>1) )
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"You cannot call for a restart after %d seconds\n\"",
+ g_mapvoteMaxTime.integer ) );
+ G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse );
+ return;
+ }
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", arg1 );
+ Com_sprintf( level.voteDisplayString,
+ sizeof( level.voteDisplayString ), "Restart current map" );
+ level.votePassThreshold = g_mapVotesPercent.integer;
+ }
+ else if( !Q_stricmp( arg1, "map" ) )
+ {
+ if( g_mapvoteMaxTime.integer
+ && (( level.time - level.startTime ) >= g_mapvoteMaxTime.integer * 1000 )
+ && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )
+ && (level.numPlayingClients > 0 && level.numConnectedClients>1) )
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"You cannot call for a mapchange after %d seconds\n\"",
+ g_mapvoteMaxTime.integer ) );
+ G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse );
+ return;
+ }
+
+ if( !G_MapExists( arg2 ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"callvote: "
+ "'maps/%s.bsp' could not be found on the server\n\"", arg2 ) );
+ return;
+ }
+
+ if( !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) && !map_is_votable( arg2 ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"callvote: "
+ "Only admins may call a vote for map: %s\n\"", arg2 ) );
+ return;
+ }
+
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 );
+ Com_sprintf( level.voteDisplayString,
+ sizeof( level.voteDisplayString ), "Change to map '%s'", arg2 );
+ level.votePassThreshold = g_mapVotesPercent.integer;
+ }
+ else if( !Q_stricmp( arg1, "nextmap" ) )
+ {
+ if( G_MapExists( g_nextMap.string ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"callvote: "
+ "the next map is already set to '%s^7'\n\"", g_nextMap.string ) );
+ return;
+ }
+
+ if( !arg2[ 0 ] )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callvote: you must specify a map\n\"" );
+ return;
+ }
+
+ if( !G_MapExists( arg2 ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"callvote: "
+ "'maps/%s^7.bsp' could not be found on the server\n\"", arg2 ) );
+ return;
+ }
+
+ if( !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) && !map_is_votable( arg2 ) )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"callvote: "
+ "Only admins may call a vote for map: %s\n\"", arg2 ) );
+ return;
+ }
+
+ Com_sprintf( level.voteString, sizeof( level.voteString ),
+ "set g_nextMap %s", arg2 );
+ Com_sprintf( level.voteDisplayString,
+ sizeof( level.voteDisplayString ), "Set the next map to '%s^7'", arg2 );
+ level.votePassThreshold = g_mapVotesPercent.integer;
+ }
+ else if( !Q_stricmp( arg1, "draw" ) )
+ {
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "evacuation" );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ),
+ "End match in a draw" );
+ level.votePassThreshold = g_mapVotesPercent.integer;
+ }
+ else if( !Q_stricmp( arg1, "poll" ) )
+ {
+ if( arg2plus[ 0 ] == '\0' )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"callvote: You forgot to specify what people should vote on.\n\"" );
+ return;
+ }
+ level.voteString[ 0 ] = '\0';
+ Com_sprintf( level.voteDisplayString,
+ sizeof( level.voteDisplayString ), "[Poll] \'%s\'", arg2plus );
+ }
+ else if( !Q_stricmp( arg1, "sudden_death" ) ||
+ !Q_stricmp( arg1, "suddendeath" ) )
+ {
+ if(!g_suddenDeathVotePercent.integer)
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Sudden Death votes have been disabled\n\"" );
+ return;
+ }
+ else if( g_suddenDeath.integer )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"callvote: Sudden Death has already begun\n\"") );
+ return;
+ }
+ else if( G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "print \"callvote: Sudden Death is already immenent\n\"") );
+ return;
+ }
+ else
+ {
+ level.votePassThreshold = g_suddenDeathVotePercent.integer;
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "suddendeath" );
+ Com_sprintf( level.voteDisplayString,
+ sizeof( level.voteDisplayString ), "Begin sudden death" );
+
+ if( g_suddenDeathVoteDelay.integer )
+ Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " in %d seconds", g_suddenDeathVoteDelay.integer ) );
+
+ }
+ }
+ else if( !Q_stricmp( arg1, "extend" ) )
+ {
+ if( !g_extendVotesPercent.integer )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Extend votes have been disabled\n\"" );
+ return;
+ }
+ if( g_extendVotesCount.integer
+ && level.extend_vote_count >= g_extendVotesCount.integer )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"callvote: Maximum number of %d extend votes has been reached\n\"",
+ g_extendVotesCount.integer ) );
+ return;
+ }
+ if( !g_timelimit.integer ) {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"This match has no timelimit so extend votes wont work\n\"" );
+ return;
+ }
+ if( level.time - level.startTime <
+ ( g_timelimit.integer - g_extendVotesTime.integer / 2 ) * 60000 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"callvote: Extend votes only allowed with less than %d minutes remaining\n\"",
+ g_extendVotesTime.integer / 2 ) );
+ return;
+ }
+ level.extend_vote_count++;
+ level.votePassThreshold = g_extendVotesPercent.integer;
+ Com_sprintf( level.voteString, sizeof( level.voteString ),
+ "timelimit %i", g_timelimit.integer + g_extendVotesTime.integer );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ),
+ "Extend the timelimit by %d minutes", g_extendVotesTime.integer );
+ }
+ else
+ {
+ qboolean match = qfalse;
+ char customVoteKeys[ MAX_STRING_CHARS ];
+
+ customVoteKeys[ 0 ] = '\0';
+ if( g_customVotePercent.integer )
+ {
+ char text[ MAX_STRING_CHARS ];
+ char *votekey, *votemsg, *votecmd, *voteperc;
+ int votePValue;
+
+ text[ 0 ] = '\0';
+ for( i = 0; i < CUSTOM_VOTE_COUNT; i++ )
+ {
+ switch( i )
+ {
+ case 0:
+ Q_strncpyz( text, g_customVote1.string, sizeof( text ) );
+ break;
+ case 1:
+ Q_strncpyz( text, g_customVote2.string, sizeof( text ) );
+ break;
+ case 2:
+ Q_strncpyz( text, g_customVote3.string, sizeof( text ) );
+ break;
+ case 3:
+ Q_strncpyz( text, g_customVote4.string, sizeof( text ) );
+ break;
+ case 4:
+ Q_strncpyz( text, g_customVote5.string, sizeof( text ) );
+ break;
+ case 5:
+ Q_strncpyz( text, g_customVote6.string, sizeof( text ) );
+ break;
+ case 6:
+ Q_strncpyz( text, g_customVote7.string, sizeof( text ) );
+ break;
+ case 7:
+ Q_strncpyz( text, g_customVote8.string, sizeof( text ) );
+ break;
+ }
+ if ( text[ 0 ] == '\0' )
+ continue;
+
+ // custom vote cvar format: "callvote_name,Vote message string,vote success command[,percent]"
+ votekey = text;
+ votemsg = strchr( votekey, ',' );
+ if( !votemsg || *votemsg != ',' )
+ continue;
+ *votemsg = '\0';
+ votemsg++;
+ Q_strcat( customVoteKeys, sizeof( customVoteKeys ),
+ va( "%s%s", ( customVoteKeys[ 0 ] == '\0' ) ? "" : ", ", votekey ) );
+ votecmd = strchr( votemsg, ',' );
+ if( !votecmd || *votecmd != ',' )
+ continue;
+ *votecmd = '\0';
+ votecmd++;
+
+ voteperc = strchr( votecmd, ',' );
+ if( !voteperc || *voteperc != ',' )
+ votePValue = g_customVotePercent.integer;
+ else
+ {
+ *voteperc = '\0';
+ voteperc++;
+ votePValue = atoi( voteperc );
+ if( !votePValue )
+ votePValue = g_customVotePercent.integer;
+ }
+
+ if( Q_stricmp( arg1, votekey ) != 0 )
+ continue;
+ if( votemsg[ 0 ] == '\0' || votecmd[ 0 ] == '\0' )
+ continue;
+
+ level.votePassThreshold = votePValue;
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", votecmd );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", votemsg );
+ match = qtrue;
+ break;
+ }
+ }
+
+ if( !match )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" );
+ trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: "
+ "map, map_restart, draw, extend, nextmap, kick, spec, mute, unmute, poll, and sudden_death\n" );
+ if( customVoteKeys[ 0 ] != '\0' )
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Additional custom vote commands: %s\n\"", customVoteKeys ) );
+ return;
+ }
+ }
+
+ if( level.votePassThreshold!=50 )
+ {
+ Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( "^7 (Needs > %d percent)", level.votePassThreshold ) );
+ }
+
+ if ( reason[0]!='\0' )
+ Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " Reason: '%s^7'", reason ) );
+
+ G_admin_adminlog_log( ent, "vote", NULL, 0, qtrue );
+
+ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE
+ " called a vote: %s" S_COLOR_WHITE "\n\"", ent->client->pers.netname, level.voteDisplayString ) );
+
+ G_LogPrintf("Vote: %s^7 called a vote: %s^7\n", ent->client->pers.netname, level.voteDisplayString );
+
+ Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " Called by: '%s^7'", ent->client->pers.netname ) );
+
+ ent->client->pers.voteCount++;
+
+ // start the voting, the caller autoamtically votes yes
+ level.voteTime = level.time;
+ level.voteNo = 0;
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ level.clients[i].ps.eFlags &= ~EF_VOTED;
+
+ if( !Q_stricmp( arg1, "poll" ) )
+ {
+ level.voteYes = 0;
+ }
+ else
+ {
+ level.voteYes = 1;
+ ent->client->ps.eFlags |= EF_VOTED;
+ }
+
+ trap_SetConfigstring( CS_VOTE_TIME, va( "%i", level.voteTime ) );
+ trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString );
+ trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) );
+ trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) );
+}
+
+
+/*
+==================
+Cmd_Vote_f
+==================
+*/
+void Cmd_Vote_f( gentity_t *ent )
+{
+ char msg[ 64 ];
+
+ if ( level.intermissiontime || level.paused )
+ {
+ if( level.mapRotationVoteTime )
+ {
+ trap_Argv( 1, msg, sizeof( msg ) );
+ if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' )
+ G_IntermissionMapVoteCommand( ent, qfalse, qtrue );
+ }
+
+ return;
+ }
+
+ if( !level.voteTime )
+ {
+ if( ent->client->pers.teamSelection != PTE_NONE )
+ {
+ // If there is a teamvote going on but no global vote, forward this vote on as a teamvote
+ // (ugly hack for 1.1 cgames + noobs who can't figure out how to use any command that isn't bound by default)
+ int cs_offset = 0;
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ cs_offset = 1;
+
+ if( level.teamVoteTime[ cs_offset ] )
+ {
+ if( !(ent->client->ps.eFlags & EF_TEAMVOTED ) )
+ {
+ Cmd_TeamVote_f(ent);
+ return;
+ }
+ }
+ }
+ trap_SendServerCommand( ent-g_entities, "print \"No vote in progress\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.eFlags & EF_VOTED )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Vote already cast\n\"" );
+ return;
+ }
+
+ trap_SendServerCommand( ent-g_entities, "print \"Vote cast\n\"" );
+
+ ent->client->ps.eFlags |= EF_VOTED;
+
+ trap_Argv( 1, msg, sizeof( msg ) );
+
+ if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' )
+ {
+ level.voteYes++;
+ trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) );
+ }
+ else
+ {
+ level.voteNo++;
+ trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) );
+ }
+
+ // a majority will be determined in G_CheckVote, which will also account
+ // for players entering or leaving
+}
+
+/*
+==================
+Cmd_CallTeamVote_f
+==================
+*/
+void Cmd_CallTeamVote_f( gentity_t *ent )
+{
+ int i, team, cs_offset = 0;
+ char arg1[ MAX_STRING_TOKENS ];
+ char arg2[ MAX_STRING_TOKENS ];
+ int clientNum = -1;
+ char name[ MAX_NETNAME ];
+ char message[ MAX_STRING_CHARS ];
+ char targetname[ MAX_NAME_LENGTH] = "";
+ char reason[ MAX_STRING_CHARS ] = "";
+ char *arg1plus;
+ char *arg2plus;
+ char *ptr = NULL;
+ int numVoters = 0;
+
+ arg1plus = G_SayConcatArgs( 1 );
+ arg2plus = G_SayConcatArgs( 2 );
+
+ team = ent->client->pers.teamSelection;
+
+ if( team == PTE_ALIENS )
+ cs_offset = 1;
+
+ if(team==PTE_ALIENS)
+ numVoters = level.numAlienClients;
+ else if(team==PTE_HUMANS)
+ numVoters = level.numHumanClients;
+
+ if( !g_allowVote.integer )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" );
+ return;
+ }
+
+ if( level.teamVoteTime[ cs_offset ] )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress\n\"" );
+ return;
+ }
+
+ //see if they can vote
+ if( G_admin_permission( ent, ADMF_NO_VOTE ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" );
+ return;
+ }
+
+ if( g_voteLimit.integer > 0
+ && ent->client->pers.voteCount >= g_voteLimit.integer
+ && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"You have already called the maximum number of votes (%d)\n\"",
+ g_voteLimit.integer ) );
+ return;
+ }
+
+ if( G_IsMuted( ent->client ) )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "print \"You are muted and cannot call teamvotes\n\"" );
+ return;
+ }
+
+ if( g_voteMinTime.integer
+ && ent->client->pers.firstConnect
+ && level.time - ent->client->pers.enterTime < g_voteMinTime.integer * 1000
+ && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )
+ && (level.numPlayingClients > 0 && level.numConnectedClients>1) )
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"You must wait %d seconds after connecting before calling a vote\n\"",
+ g_voteMinTime.integer ) );
+ return;
+ }
+
+ // make sure it is a valid command to vote on
+ trap_Argv( 1, arg1, sizeof( arg1 ) );
+ trap_Argv( 2, arg2, sizeof( arg2 ) );
+
+ if( strchr( arg1plus, ';' ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Invalid team vote string\n\"" );
+ return;
+ }
+
+ ptr = strstr(arg1plus, " -");
+ if( ptr )
+ {
+ *ptr = '\0';
+ ptr+=2;
+
+ if( *ptr == 'r' || *ptr=='R' )
+ {
+ ptr++;
+ while( *ptr == ' ' )
+ ptr++;
+ strcpy(reason, ptr);
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"callteamvote: Warning: invalid argument specified \n\"" );
+ }
+ }
+
+ // detect clientNum for partial name match votes
+ if( !Q_stricmp( arg1, "kick" ) ||
+ !Q_stricmp( arg1, "denybuild" ) ||
+ !Q_stricmp( arg1, "allowbuild" ) ||
+ !Q_stricmp( arg1, "designate" ) ||
+ !Q_stricmp( arg1, "undesignate" ) )
+ {
+ int clientNums[ MAX_CLIENTS ] = { -1 };
+ int numMatches=0;
+ char err[ MAX_STRING_CHARS ];
+
+ Q_strncpyz(targetname, arg2plus, sizeof(targetname));
+ ptr = strstr(targetname, " -");
+ if( ptr )
+ *ptr = '\0';
+
+ if( g_requireVoteReasons.integer && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) && !Q_stricmp( arg1, "kick" ) && reason[ 0 ]=='\0' )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"callvote: You must specify a reason. Use /callteamvote kick [player] -r [reason] \n\"" );
+ return;
+ }
+
+
+ if( !arg2[ 0 ] )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: no target\n\"" );
+ return;
+ }
+
+ numMatches = G_ClientNumbersFromString( targetname, clientNums );
+ if( numMatches == 1 )
+ {
+ // there was only one partial name match
+ clientNum = clientNums[ 0 ];
+ }
+ else
+ {
+ // look for an exact name match (sets clientNum to -1 if it fails)
+ clientNum = G_ClientNumberFromString( ent, targetname );
+ }
+
+ if( clientNum==-1 && numMatches > 1 )
+ {
+ G_MatchOnePlayer( clientNums, err, sizeof( err ) );
+ ADMP( va( "^3callteamvote: ^7%s\n", err ) );
+ return;
+ }
+
+ // make sure this player is on the same team
+ if( clientNum != -1 && level.clients[ clientNum ].pers.teamSelection !=
+ team )
+ {
+ clientNum = -1;
+ }
+
+ if( clientNum != -1 &&
+ level.clients[ clientNum ].pers.connected == CON_DISCONNECTED )
+ {
+ clientNum = -1;
+ }
+
+ if( clientNum != -1 )
+ {
+ Q_strncpyz( name, level.clients[ clientNum ].pers.netname,
+ sizeof( name ) );
+ Q_CleanStr( name );
+ if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) )
+ {
+ char reasonprint[ MAX_STRING_CHARS ] = "";
+ if( reason[ 0 ] != '\0' )
+ Com_sprintf(reasonprint, sizeof(reasonprint), "With reason: %s", reason);
+
+ Com_sprintf( message, sizeof( message ), "%s^7 attempted /callteamvote %s %s on immune admin %s^7 %s^7",
+ ent->client->pers.netname, arg1, arg2, g_entities[ clientNum ].client->pers.netname, reasonprint );
+ }
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: invalid player\n\"" );
+ return;
+ }
+ }
+
+ if( !Q_stricmp( arg1, "kick" ) )
+ {
+ if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: admin is immune from vote kick\n\"" );
+ G_AdminsPrintf("%s\n",message);
+ G_admin_adminlog_log( ent, "teamvote", NULL, 0, qfalse );
+ return;
+ }
+
+
+ // use ip in case this player disconnects before the vote ends
+ Com_sprintf( level.teamVoteString[ cs_offset ],
+ sizeof( level.teamVoteString[ cs_offset ] ),
+ "!ban %s \"%s\" team vote kick", level.clients[ clientNum ].pers.ip,
+ g_adminTempBan.string );
+ Com_sprintf( level.teamVoteDisplayString[ cs_offset ],
+ sizeof( level.teamVoteDisplayString[ cs_offset ] ),
+ "Kick player '%s'", name );
+ }
+ else if( !Q_stricmp( arg1, "denybuild" ) )
+ {
+ if( level.clients[ clientNum ].pers.denyBuild )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: player already lost building rights\n\"" );
+ return;
+ }
+
+ if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: admin is immune from denybuild\n\"" );
+ G_AdminsPrintf("%s\n",message);
+ G_admin_adminlog_log( ent, "teamvote", NULL, 0, qfalse );
+ return;
+ }
+
+ Com_sprintf( level.teamVoteString[ cs_offset ],
+ sizeof( level.teamVoteString[ cs_offset ] ), "!denybuild %i", clientNum );
+ Com_sprintf( level.teamVoteDisplayString[ cs_offset ],
+ sizeof( level.teamVoteDisplayString[ cs_offset ] ),
+ "Take away building rights from '%s'", name );
+ }
+ else if( !Q_stricmp( arg1, "allowbuild" ) )
+ {
+ if( !level.clients[ clientNum ].pers.denyBuild )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: player already has building rights\n\"" );
+ return;
+ }
+
+ Com_sprintf( level.teamVoteString[ cs_offset ],
+ sizeof( level.teamVoteString[ cs_offset ] ), "!allowbuild %i", clientNum );
+ Com_sprintf( level.teamVoteDisplayString[ cs_offset ],
+ sizeof( level.teamVoteDisplayString[ cs_offset ] ),
+ "Allow '%s' to build", name );
+ }
+ else if( !Q_stricmp( arg1, "designate" ) )
+ {
+ if( !g_designateVotes.integer )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: Designate votes have been disabled.\n\"" );
+ return;
+ }
+
+ if( level.clients[ clientNum ].pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: player is already a designated builder\n\"" );
+ return;
+ }
+ Com_sprintf( level.teamVoteString[ cs_offset ],
+ sizeof( level.teamVoteString[ cs_offset ] ), "!designate %i", clientNum );
+ Com_sprintf( level.teamVoteDisplayString[ cs_offset ],
+ sizeof( level.teamVoteDisplayString[ cs_offset ] ),
+ "Make '%s' a designated builder", name );
+ }
+ else if( !Q_stricmp( arg1, "undesignate" ) )
+ {
+
+ if( !g_designateVotes.integer )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: Designate votes have been disabled.\n\"" );
+ return;
+ }
+
+ if( !level.clients[ clientNum ].pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: player is not currently a designated builder\n\"" );
+ return;
+ }
+ Com_sprintf( level.teamVoteString[ cs_offset ],
+ sizeof( level.teamVoteString[ cs_offset ] ), "!undesignate %i", clientNum );
+ Com_sprintf( level.teamVoteDisplayString[ cs_offset ],
+ sizeof( level.teamVoteDisplayString[ cs_offset ] ),
+ "Remove designated builder status from '%s'", name );
+ }
+ else if( !Q_stricmp( arg1, "admitdefeat" ) )
+ {
+ if( numVoters <=1 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"callteamvote: You cannot admitdefeat by yourself. Use /callvote draw.\n\"" );
+ return;
+ }
+
+ Com_sprintf( level.teamVoteString[ cs_offset ],
+ sizeof( level.teamVoteString[ cs_offset ] ), "admitdefeat %i", team );
+ Com_sprintf( level.teamVoteDisplayString[ cs_offset ],
+ sizeof( level.teamVoteDisplayString[ cs_offset ] ),
+ "Admit Defeat" );
+ }
+ else if( !Q_stricmp( arg1, "poll" ) )
+ {
+ if( arg2plus[ 0 ] == '\0' )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"callteamvote: You forgot to specify what people should vote on.\n\"" );
+ return;
+ }
+ level.teamVoteString[ cs_offset ][ 0 ] = '\0';
+ Com_sprintf( level.teamVoteDisplayString[ cs_offset ],
+ sizeof( level.voteDisplayString ), "[Poll] \'%s\'", arg2plus );
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" );
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Valid team vote commands are: "
+ "kick, denybuild, allowbuild, poll, designate, undesignate, and admitdefeat\n\"" );
+ return;
+ }
+ ent->client->pers.voteCount++;
+
+ G_admin_adminlog_log( ent, "teamvote", arg1, 0, qtrue );
+
+ if ( reason[0]!='\0' )
+ Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), va( " Reason: '%s'^7", reason ) );
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team )
+ {
+ trap_SendServerCommand( i, va("print \"%s " S_COLOR_WHITE
+ "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) );
+ }
+ else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) &&
+ ( ( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) ) ||
+ level.clients[ i ].pers.teamSelection == PTE_NONE ) )
+ {
+ trap_SendServerCommand( i, va("print \"^6[Admins]^7 %s " S_COLOR_WHITE
+ "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) );
+ }
+ }
+
+ if(team==PTE_ALIENS)
+ G_LogPrintf("Teamvote: %s^7 called a teamvote (aliens): %s^7\n", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] );
+ else if(team==PTE_HUMANS)
+ G_LogPrintf("Teamvote: %s^7 called a teamvote (humans): %s^7\n", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] );
+
+ Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), va( " Called by: '%s^7'", ent->client->pers.netname ) );
+
+ // start the voting, the caller autoamtically votes yes
+ level.teamVoteTime[ cs_offset ] = level.time;
+ level.teamVoteNo[ cs_offset ] = 0;
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team )
+ level.clients[ i ].ps.eFlags &= ~EF_TEAMVOTED;
+ }
+
+ if( !Q_stricmp( arg1, "poll" ) )
+ {
+ level.teamVoteYes[ cs_offset ] = 0;
+ }
+ else
+ {
+ level.teamVoteYes[ cs_offset ] = 1;
+ ent->client->ps.eFlags |= EF_TEAMVOTED;
+ }
+
+ trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va( "%i", level.teamVoteTime[ cs_offset ] ) );
+ trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteDisplayString[ cs_offset ] );
+ trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) );
+ trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) );
+}
+
+
+/*
+==================
+Cmd_TeamVote_f
+==================
+*/
+void Cmd_TeamVote_f( gentity_t *ent )
+{
+ int cs_offset = 0;
+ char msg[ 64 ];
+
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ cs_offset = 1;
+
+ if( !level.teamVoteTime[ cs_offset ] )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"No team vote in progress\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.eFlags & EF_TEAMVOTED )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Team vote already cast\n\"" );
+ return;
+ }
+
+ trap_SendServerCommand( ent-g_entities, "print \"Team vote cast\n\"" );
+
+ ent->client->ps.eFlags |= EF_TEAMVOTED;
+
+ trap_Argv( 1, msg, sizeof( msg ) );
+
+ if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' )
+ {
+ level.teamVoteYes[ cs_offset ]++;
+ trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) );
+ }
+ else
+ {
+ level.teamVoteNo[ cs_offset ]++;
+ trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) );
+ }
+
+ // a majority will be determined in TeamCheckVote, which will also account
+ // for players entering or leaving
+}
+
+
+/*
+=================
+Cmd_SetViewpos_f
+=================
+*/
+void Cmd_SetViewpos_f( gentity_t *ent )
+{
+ vec3_t origin, angles;
+ char buffer[ MAX_TOKEN_CHARS ];
+ int i;
+
+ if( trap_Argc( ) != 5 )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"usage: setviewpos x y z yaw\n\"" ) );
+ return;
+ }
+
+ VectorClear( angles );
+
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ trap_Argv( i + 1, buffer, sizeof( buffer ) );
+ origin[ i ] = atof( buffer );
+ }
+
+ trap_Argv( 4, buffer, sizeof( buffer ) );
+ angles[ YAW ] = atof( buffer );
+
+ TeleportPlayer( ent, origin, angles );
+}
+
+#define AS_OVER_RT3 ((ALIENSENSE_RANGE*0.5f)/M_ROOT3)
+
+qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin )
+{
+ vec3_t fromMins, fromMaxs;
+ vec3_t toMins, toMaxs;
+ vec3_t temp;
+ trace_t tr;
+ float nudgeHeight;
+ float maxHorizGrowth;
+ pClass_t oldClass = ent->client->ps.stats[ STAT_PCLASS ];
+
+ BG_FindBBoxForClass( oldClass, fromMins, fromMaxs, NULL, NULL, NULL );
+ BG_FindBBoxForClass( class, toMins, toMaxs, NULL, NULL, NULL );
+
+ VectorCopy( ent->s.origin, newOrigin );
+
+ // find max x/y diff
+ maxHorizGrowth = toMaxs[ 0 ] - fromMaxs[ 0 ];
+ if( toMaxs[ 1 ] - fromMaxs[ 1 ] > maxHorizGrowth )
+ maxHorizGrowth = toMaxs[ 1 ] - fromMaxs[ 1 ];
+ if( toMins[ 0 ] - fromMins[ 0 ] > -maxHorizGrowth )
+ maxHorizGrowth = -( toMins[ 0 ] - fromMins[ 0 ] );
+ if( toMins[ 1 ] - fromMins[ 1 ] > -maxHorizGrowth )
+ maxHorizGrowth = -( toMins[ 1 ] - fromMins[ 1 ] );
+
+ if( maxHorizGrowth > 0.0f )
+ {
+ // test by moving the player up the max required on a 60 degree slope
+ nudgeHeight = maxHorizGrowth * 2.0f;
+ }
+ else
+ {
+ // player is shrinking, so there's no need to nudge them upwards
+ nudgeHeight = 0.0f;
+ }
+
+ // find what the new origin would be on a level surface
+ newOrigin[ 2 ] += fabs( toMins[ 2 ] ) - fabs( fromMins[ 2 ] );
+
+ //compute a place up in the air to start the real trace
+ VectorCopy( newOrigin, temp );
+ temp[ 2 ] += nudgeHeight;
+ trap_Trace( &tr, newOrigin, toMins, toMaxs, temp, ent->s.number, MASK_PLAYERSOLID );
+
+ //trace down to the ground so that we can evolve on slopes
+ VectorCopy( newOrigin, temp );
+ temp[ 2 ] += ( nudgeHeight * tr.fraction );
+ trap_Trace( &tr, temp, toMins, toMaxs, newOrigin, ent->s.number, MASK_PLAYERSOLID );
+ VectorCopy( tr.endpos, newOrigin );
+
+ //make REALLY sure
+ trap_Trace( &tr, newOrigin, toMins, toMaxs, newOrigin,
+ ent->s.number, MASK_PLAYERSOLID );
+
+ //check there is room to evolve
+ if( !tr.startsolid && tr.fraction == 1.0f )
+ return qtrue;
+ else
+ return qfalse;
+}
+
+/*
+=================
+Cmd_Class_f
+=================
+*/
+void Cmd_Class_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int clientNum;
+ int i;
+ vec3_t infestOrigin;
+ pClass_t currentClass = ent->client->pers.classSelection;
+ pClass_t newClass;
+ int numLevels;
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { AS_OVER_RT3, AS_OVER_RT3, AS_OVER_RT3 };
+ vec3_t mins, maxs;
+ int num;
+ gentity_t *other;
+ qboolean humanNear = qfalse;
+
+
+ clientNum = ent->client - level.clients;
+ trap_Argv( 1, s, sizeof( s ) );
+ newClass = BG_FindClassNumForName( s );
+
+ if( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
+ {
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ {
+ if( newClass != PCL_ALIEN_BUILDER0 &&
+ newClass != PCL_ALIEN_BUILDER0_UPG &&
+ newClass != PCL_ALIEN_LEVEL0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"You cannot spawn with class %s\n\"", s ) );
+ return;
+ }
+
+ if( !BG_ClassIsAllowed( newClass ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Class %s is not allowed\n\"", s ) );
+ return;
+ }
+
+ if( !BG_FindStagesForClass( newClass, g_alienStage.integer ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Class %s not allowed at stage %d\n\"",
+ s, g_alienStage.integer ) );
+ return;
+ }
+
+ if( ent->client->pers.denyBuild && ( newClass==PCL_ALIEN_BUILDER0 || newClass==PCL_ALIEN_BUILDER0_UPG ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your building rights have been revoked\n\"" );
+ return;
+ }
+
+ // spawn from an egg
+ if( G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ) )
+ {
+ ent->client->pers.classSelection = newClass;
+ ent->client->ps.stats[ STAT_PCLASS ] = newClass;
+ }
+ }
+ else if( ent->client->pers.teamSelection == PTE_HUMANS )
+ {
+ //set the item to spawn with
+ if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) &&
+ BG_WeaponIsAllowed( WP_MACHINEGUN ) )
+ {
+ ent->client->pers.humanItemSelection = WP_MACHINEGUN;
+ }
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) &&
+ BG_WeaponIsAllowed( WP_HBUILD ) )
+ {
+ ent->client->pers.humanItemSelection = WP_HBUILD;
+ }
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) &&
+ BG_WeaponIsAllowed( WP_HBUILD2 ) &&
+ BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) )
+ {
+ ent->client->pers.humanItemSelection = WP_HBUILD2;
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Unknown starting item\n\"" );
+ return;
+ }
+ // spawn from a telenode
+ G_LogOnlyPrintf("ClientTeamClass: %i human %s\n", clientNum, s);
+ if( G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ) )
+ {
+ ent->client->pers.classSelection = PCL_HUMAN;
+ ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN;
+ }
+ }
+ return;
+ }
+
+ if( ent->health <= 0 )
+ return;
+
+ if( ent->client->pers.teamSelection == PTE_ALIENS &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) )
+ {
+ if( newClass == PCL_NONE )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Unknown class\n\"" );
+ return;
+ }
+
+ //if we are not currently spectating, we are attempting evolution
+ if( ent->client->pers.classSelection != PCL_NONE )
+ {
+ if( ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) ||
+ ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You cannot evolve while wallwalking\n\"" );
+ return;
+ }
+
+ //check there are no humans nearby
+ VectorAdd( ent->client->ps.origin, range, maxs );
+ VectorSubtract( ent->client->ps.origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ other = &g_entities[ entityList[ i ] ];
+
+ if( ( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
+ ( other->s.eType == ET_BUILDABLE && other->biteam == BIT_HUMANS ) )
+ {
+ humanNear = qtrue;
+ }
+ //If its the OM, then ignore all humans.
+ if(other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_OVERMIND)
+ {
+ humanNear = qfalse;
+ break;
+ }
+ }
+
+ if(humanNear == qtrue) {
+ G_TriggerMenu( clientNum, MN_A_TOOCLOSE );
+ return;
+ }
+
+ if( !level.overmindPresent )
+ {
+ G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE );
+ return;
+ }
+
+ // denyweapons
+ if( newClass >= PCL_ALIEN_LEVEL1 && newClass <= PCL_ALIEN_LEVEL4 &&
+ ent->client->pers.denyAlienClasses & ( 1 << newClass ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from using this class\n\"" ) );
+ return;
+ }
+
+ //guard against selling the HBUILD weapons exploit
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
+ ( currentClass == PCL_ALIEN_BUILDER0 ||
+ currentClass == PCL_ALIEN_BUILDER0_UPG ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"You cannot evolve until build timer expires\n\"" ) );
+ return;
+ }
+
+ numLevels = BG_ClassCanEvolveFromTo( currentClass,
+ newClass,
+ (short)ent->client->ps.persistant[ PERS_CREDIT ], 0 );
+
+ if( G_RoomForClassChange( ent, newClass, infestOrigin ) )
+ {
+ //...check we can evolve to that class
+ if( numLevels >= 0 &&
+ BG_FindStagesForClass( newClass, g_alienStage.integer ) &&
+ BG_ClassIsAllowed( newClass ) )
+ {
+ G_LogOnlyPrintf("ClientTeamClass: %i alien %s\n", clientNum, s);
+
+ ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] /
+ (float)BG_FindHealthForClass( currentClass );
+
+ if( ent->client->pers.evolveHealthFraction < 0.0f )
+ ent->client->pers.evolveHealthFraction = 0.0f;
+ else if( ent->client->pers.evolveHealthFraction > 1.0f )
+ ent->client->pers.evolveHealthFraction = 1.0f;
+
+ //remove credit
+ G_AddCreditToClient( ent->client, -(short)numLevels, qtrue );
+ ent->client->pers.classSelection = newClass;
+ ClientUserinfoChanged( clientNum, qfalse );
+ VectorCopy( infestOrigin, ent->s.pos.trBase );
+ ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase );
+ return;
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You cannot evolve from your current class\n\"" );
+ return;
+ }
+ }
+ else
+ {
+ G_TriggerMenu( clientNum, MN_A_NOEROOM );
+ return;
+ }
+ }
+ else if( ent->client->pers.teamSelection == PTE_HUMANS )
+ {
+ //humans cannot use this command whilst alive
+ if( ent->client->pers.classSelection != PCL_NONE )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You must be dead to use the class command\n\"" ) );
+ return;
+ }
+
+ ent->client->pers.classSelection =
+ ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN;
+
+ //set the item to spawn with
+ if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) && BG_WeaponIsAllowed( WP_MACHINEGUN ) )
+ ent->client->pers.humanItemSelection = WP_MACHINEGUN;
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && BG_WeaponIsAllowed( WP_HBUILD ) )
+ ent->client->pers.humanItemSelection = WP_HBUILD;
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && BG_WeaponIsAllowed( WP_HBUILD2 ) &&
+ BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) )
+ ent->client->pers.humanItemSelection = WP_HBUILD2;
+ else
+ {
+ ent->client->pers.classSelection = PCL_NONE;
+ trap_SendServerCommand( ent-g_entities, va( "print \"Unknown starting item\n\"" ) );
+ return;
+ }
+
+ G_LogOnlyPrintf("ClientTeamClass: %i human %s\n", clientNum, s);
+
+ G_PushSpawnQueue( &level.humanSpawnQueue, clientNum );
+ }
+ }
+}
+
+/*
+=================
+DBCommand
+
+Send command to all designated builders of selected team
+=================
+*/
+void DBCommand( pTeam_t team, const char *text )
+{
+ int i;
+ gentity_t *ent;
+
+ for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++ )
+ {
+ if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) ||
+ ( ent->client->pers.teamSelection != team ) ||
+ !ent->client->pers.designatedBuilder )
+ continue;
+
+ trap_SendServerCommand( i, text );
+ }
+}
+
+/*
+=================
+Cmd_Destroy_f
+=================
+*/
+void Cmd_Destroy_f( gentity_t *ent )
+{
+ vec3_t forward, end;
+ trace_t tr;
+ gentity_t *traceEnt;
+ char cmd[ 12 ];
+ qboolean deconstruct = qtrue;
+
+ if( ent->client->pers.denyBuild )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Your building rights have been revoked\n\"" );
+ return;
+ }
+
+ trap_Argv( 0, cmd, sizeof( cmd ) );
+ if( Q_stricmp( cmd, "destroy" ) == 0 )
+ deconstruct = qfalse;
+
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ {
+ if( ( ent->client->hovel->s.eFlags & EF_DBUILDER ) &&
+ !ent->client->pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"This structure is protected by designated builder\n\"" );
+ DBCommand( ent->client->pers.teamSelection,
+ va( "print \"%s^3 has attempted to decon a protected structure!\n\"",
+ ent->client->pers.netname ) );
+ return;
+ }
+ G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin,
+ 10000, 0, MOD_SUICIDE );
+ }
+
+ if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) )
+ {
+ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
+ VectorMA( ent->client->ps.origin, 100, forward, end );
+
+ trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( tr.fraction < 1.0f &&
+ ( traceEnt->s.eType == ET_BUILDABLE ) &&
+ ( traceEnt->biteam == ent->client->pers.teamSelection ) &&
+ ( ( ent->client->ps.weapon >= WP_ABUILD ) &&
+ ( ent->client->ps.weapon <= WP_HBUILD ) ) )
+ {
+ // Cancel deconstruction
+ if( g_markDeconstruct.integer == 1 && traceEnt->deconstruct )
+ {
+ traceEnt->deconstruct = qfalse;
+ return;
+ }
+ if( ( traceEnt->s.eFlags & EF_DBUILDER ) &&
+ !ent->client->pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"This structure is protected by designated builder\n\"" );
+ DBCommand( ent->client->pers.teamSelection,
+ va( "print \"%s^3 has attempted to decon a protected structure!\n\"",
+ ent->client->pers.netname ) );
+ return;
+ }
+
+ // Check the minimum level to deconstruct
+ if ( G_admin_level( ent ) < g_minDeconLevel.integer && !ent->client->pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You do not have deconstructuction rights.\n\"" );
+ return;
+ }
+
+ // Prevent destruction of the last spawn
+ if( g_markDeconstruct.integer != 1 && !g_cheats.integer )
+ {
+ if( ent->client->pers.teamSelection == PTE_ALIENS &&
+ traceEnt->s.modelindex == BA_A_SPAWN )
+ {
+ if( level.numAlienSpawns <= 1 )
+ return;
+ }
+ else if( ent->client->pers.teamSelection == PTE_HUMANS &&
+ traceEnt->s.modelindex == BA_H_SPAWN )
+ {
+ if( level.numHumanSpawns <= 1 )
+ return;
+ }
+ }
+
+ // Don't allow destruction of hovel with granger inside
+ if( traceEnt->s.modelindex == BA_A_HOVEL && traceEnt->active )
+ return;
+
+ // Don't allow destruction of buildables that cannot be rebuilt
+ if(g_suddenDeath.integer && traceEnt->health > 0 &&
+ ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE &&
+ !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) ||
+ ( g_suddenDeathMode.integer == SDMODE_BP &&
+ BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) ||
+ g_suddenDeathMode.integer == SDMODE_NO_BUILD ||
+ g_suddenDeathMode.integer == SDMODE_NO_DECON ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"During Sudden Death you can only decon buildings that "
+ "can be rebuilt\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
+ return;
+ }
+
+ if( traceEnt->health > 0 || g_deconDead.integer )
+ {
+ if( g_markDeconstruct.integer == 1 )
+ {
+ traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction
+ traceEnt->deconstructTime = level.time;
+ }
+ else
+ {
+ if( traceEnt->health > 0 )
+ {
+ buildHistory_t *new;
+
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 )
+ ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ent;
+ new->name[ 0 ] = 0;
+ new->buildable = traceEnt->s.modelindex;
+ VectorCopy( traceEnt->s.pos.trBase, new->origin );
+ VectorCopy( traceEnt->s.angles, new->angles );
+ VectorCopy( traceEnt->s.origin2, new->origin2 );
+ VectorCopy( traceEnt->s.angles2, new->angles2 );
+ new->fate = BF_DECONNED;
+ new->next = NULL;
+ new->marked = NULL;
+ G_LogBuild( new );
+
+ G_TeamCommand( ent->client->pers.teamSelection,
+ va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"",
+ BG_FindHumanNameForBuildable( traceEnt->s.modelindex ),
+ ent->client->pers.netname ) );
+
+ G_LogPrintf( "Decon: %i %i 0: %s^7 deconstructed %s\n",
+ ent->client->ps.clientNum,
+ traceEnt->s.modelindex,
+ ent->client->pers.netname,
+ BG_FindNameForBuildable( traceEnt->s.modelindex ) );
+ }
+
+ if( !deconstruct )
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE );
+ else
+ G_FreeEntity( traceEnt );
+
+ if( !g_cheats.integer )
+ ent->client->ps.stats[ STAT_MISC ] +=
+ BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2;
+ }
+ }
+ }
+ }
+}
+
+void Cmd_Mark_f( gentity_t *ent )
+{
+ vec3_t forward, end;
+ trace_t tr;
+ gentity_t *traceEnt;
+
+ if( g_markDeconstruct.integer != 2 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Mark is disabled\n\"" );
+ return;
+ }
+
+ if( ent->client->pers.denyBuild )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Your building rights have been revoked\n\"" );
+ return;
+ }
+
+ // Check the minimum level to deconstruct
+ if ( G_admin_level( ent ) < g_minDeconLevel.integer && !ent->client->pers.designatedBuilder && g_minDeconAffectsMark.integer > 0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You do not have deconstructuction rights.\n\"" );
+ return;
+ }
+
+ // can't mark when in hovel
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ return;
+
+ if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) )
+ {
+ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
+ VectorMA( ent->client->ps.origin, 100, forward, end );
+
+ trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( tr.fraction < 1.0f &&
+ ( traceEnt->s.eType == ET_BUILDABLE ) &&
+ ( traceEnt->biteam == ent->client->pers.teamSelection ) &&
+ ( ( ent->client->ps.weapon >= WP_ABUILD ) &&
+ ( ent->client->ps.weapon <= WP_HBUILD ) ) )
+ {
+ if( ( traceEnt->s.eFlags & EF_DBUILDER ) &&
+ !ent->client->pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"this structure is protected by a designated builder\n\"" );
+ return;
+ }
+
+ // Cancel deconstruction
+ if( traceEnt->deconstruct )
+ {
+ traceEnt->deconstruct = qfalse;
+
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"%s no longer marked for deconstruction\n\"",
+ BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) );
+ return;
+ }
+
+ // Don't allow marking of buildables that cannot be rebuilt
+ if(g_suddenDeath.integer && traceEnt->health > 0 &&
+ ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE &&
+ !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) ||
+ ( g_suddenDeathMode.integer == SDMODE_BP &&
+ BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) ||
+ g_suddenDeathMode.integer == SDMODE_NO_BUILD ||
+ g_suddenDeathMode.integer == SDMODE_NO_DECON ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"During Sudden Death you can only mark buildings that "
+ "can be rebuilt\n\"" );
+ return;
+ }
+
+ if( traceEnt->health > 0 )
+ {
+ traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction
+ traceEnt->deconstructTime = level.time;
+
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"%s marked for deconstruction\n\"",
+ BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) );
+ }
+ }
+ }
+}
+
+
+/*
+=================
+Cmd_ActivateItem_f
+
+Activate an item
+=================
+*/
+void Cmd_ActivateItem_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int upgrade, weapon;
+
+ trap_Argv( 1, s, sizeof( s ) );
+ upgrade = BG_FindUpgradeNumForName( s );
+ weapon = BG_FindWeaponNumForName( s );
+
+ if( upgrade != UP_NONE && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ BG_ActivateUpgrade( upgrade, ent->client->ps.stats );
+ else if( weapon != WP_NONE && BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) )
+ G_ForceWeaponChange( ent, weapon );
+ else
+ trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) );
+}
+
+
+/*
+=================
+Cmd_DeActivateItem_f
+
+Deactivate an item
+=================
+*/
+void Cmd_DeActivateItem_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int upgrade;
+
+ trap_Argv( 1, s, sizeof( s ) );
+ upgrade = BG_FindUpgradeNumForName( s );
+
+ if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ BG_DeactivateUpgrade( upgrade, ent->client->ps.stats );
+ else
+ trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) );
+}
+
+
+/*
+=================
+Cmd_ToggleItem_f
+=================
+*/
+void Cmd_ToggleItem_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int upgrade, weapon, i;
+
+ trap_Argv( 1, s, sizeof( s ) );
+ upgrade = BG_FindUpgradeNumForName( s );
+ weapon = BG_FindWeaponNumForName( s );
+
+ if( weapon != WP_NONE )
+ {
+ //special case to allow switching between
+ //the blaster and the primary weapon
+
+ if( ent->client->ps.weapon != WP_BLASTER )
+ weapon = WP_BLASTER;
+ else
+ {
+ //find a held weapon which isn't the blaster
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( i == WP_BLASTER )
+ continue;
+
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) )
+ {
+ weapon = i;
+ break;
+ }
+ }
+
+ if( i == WP_NUM_WEAPONS )
+ weapon = WP_BLASTER;
+ }
+
+ G_ForceWeaponChange( ent, weapon );
+ }
+ else if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ {
+ if( BG_UpgradeIsActive( upgrade, ent->client->ps.stats ) )
+ BG_DeactivateUpgrade( upgrade, ent->client->ps.stats );
+ else
+ BG_ActivateUpgrade( upgrade, ent->client->ps.stats );
+ }
+ else
+ trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) );
+}
+
+/*
+=================
+Cmd_Buy_f
+=================
+*/
+void Cmd_Buy_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int i;
+ int weapon, upgrade, numItems = 0;
+ int maxAmmo, maxClips;
+ qboolean buyingEnergyAmmo = qfalse;
+ qboolean hasEnergyWeapon = qfalse;
+
+ for( i = UP_NONE; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) )
+ numItems++;
+ }
+
+ for( i = WP_NONE; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) )
+ {
+ if( BG_FindUsesEnergyForWeapon( i ) )
+ hasEnergyWeapon = qtrue;
+ numItems++;
+ }
+ }
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ weapon = BG_FindWeaponNumForName( s );
+ upgrade = BG_FindUpgradeNumForName( s );
+
+ //special case to keep norf happy
+ if( weapon == WP_NONE && upgrade == UP_AMMO )
+ {
+ buyingEnergyAmmo = hasEnergyWeapon;
+ }
+
+ if( buyingEnergyAmmo )
+ {
+ //no armoury nearby
+ if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) &&
+ !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) &&
+ !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"You must be near a reactor, repeater or armoury\n\"" ) );
+ return;
+ }
+ }
+ else
+ {
+ //no armoury nearby
+ if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) );
+ return;
+ }
+ }
+
+ if( weapon != WP_NONE )
+ {
+ //already got this?
+ if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD );
+ return;
+ }
+
+ // denyweapons
+ if( weapon >= WP_PAIN_SAW && weapon <= WP_GRENADE &&
+ ent->client->pers.denyHumanWeapons & ( 1 << (weapon - WP_BLASTER) ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from buying this weapon\n\"" ) );
+ return;
+ }
+
+ //can afford this?
+ if( BG_FindPriceForWeapon( weapon ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS );
+ return;
+ }
+
+ //have space to carry this?
+ if( BG_FindSlotsForWeapon( weapon ) & ent->client->ps.stats[ STAT_SLOTS ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS );
+ return;
+ }
+
+ if( BG_FindTeamForWeapon( weapon ) != WUT_HUMANS )
+ {
+ //shouldn't need a fancy dialog
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindPurchasableForWeapon( weapon ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindStagesForWeapon( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ //add to inventory
+ BG_AddWeaponToInventory( weapon, ent->client->ps.stats );
+ BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips );
+
+ if( BG_FindUsesEnergyForWeapon( weapon ) &&
+ BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
+ maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
+
+ ent->client->ps.ammo = maxAmmo;
+ ent->client->ps.clips = maxClips;
+
+ G_ForceWeaponChange( ent, weapon );
+
+ //set build delay/pounce etc to 0
+ ent->client->ps.stats[ STAT_MISC ] = 0;
+
+ //subtract from funds
+ G_AddCreditToClient( ent->client, -(short)BG_FindPriceForWeapon( weapon ), qfalse );
+ }
+ else if( upgrade != UP_NONE )
+ {
+ //already got this?
+ if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD );
+ return;
+ }
+
+ // denyweapons
+ if( upgrade == UP_GRENADE &&
+ ent->client->pers.denyHumanWeapons & ( 1 << (WP_GRENADE - WP_BLASTER) ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from buying this upgrade\n\"" ) );
+ return;
+ }
+
+ //can afford this?
+ if( BG_FindPriceForUpgrade( upgrade ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS );
+ return;
+ }
+
+ //have space to carry this?
+ if( BG_FindSlotsForUpgrade( upgrade ) & ent->client->ps.stats[ STAT_SLOTS ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS );
+ return;
+ }
+
+ if( BG_FindTeamForUpgrade( upgrade ) != WUT_HUMANS )
+ {
+ //shouldn't need a fancy dialog
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindPurchasableForUpgrade( upgrade ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindStagesForUpgrade( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ if( upgrade == UP_BATTLESUIT && ent->client->ps.pm_flags & PMF_DUCKED )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"You can't buy this item while crouching\n\"" ) );
+ return;
+ }
+
+ if( upgrade == UP_AMMO )
+ G_GiveClientMaxAmmo( ent, buyingEnergyAmmo );
+ else
+ {
+ //add to inventory
+ BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats );
+ }
+
+ if( upgrade == UP_BATTPACK )
+ G_GiveClientMaxAmmo( ent, qtrue );
+
+ //subtract from funds
+ G_AddCreditToClient( ent->client, -(short)BG_FindPriceForUpgrade( upgrade ), qfalse );
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"Unknown item\n\"" ) );
+ }
+
+ if( trap_Argc( ) >= 2 )
+ {
+ trap_Argv( 2, s, sizeof( s ) );
+
+ //retrigger the armoury menu
+ if( !Q_stricmp( s, "retrigger" ) )
+ ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES;
+ }
+ if( ent->client->pers.paused )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You may not deconstruct while paused\n\"" );
+ return;
+ }
+
+ //update ClientInfo
+ ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );
+}
+
+
+/*
+=================
+Cmd_Sell_f
+=================
+*/
+void Cmd_Sell_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int i;
+ int weapon, upgrade;
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ //no armoury nearby
+ if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) );
+ return;
+ }
+
+ weapon = BG_FindWeaponNumForName( s );
+ upgrade = BG_FindUpgradeNumForName( s );
+
+ if( weapon != WP_NONE )
+ {
+ //are we /allowed/ to sell this?
+ if( !BG_FindPurchasableForWeapon( weapon ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't sell this weapon\n\"" ) );
+ return;
+ }
+
+ //remove weapon if carried
+ if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) )
+ {
+ //guard against selling the HBUILD weapons exploit
+ if( ( weapon == WP_HBUILD || weapon == WP_HBUILD2 ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) );
+ return;
+ }
+
+ BG_RemoveWeaponFromInventory( weapon, ent->client->ps.stats );
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( weapon ), qfalse );
+ }
+
+ //if we have this weapon selected, force a new selection
+ if( weapon == ent->client->ps.weapon )
+ G_ForceWeaponChange( ent, WP_NONE );
+ }
+ else if( upgrade != UP_NONE )
+ {
+ //are we /allowed/ to sell this?
+ if( !BG_FindPurchasableForUpgrade( upgrade ) )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"You can't sell this item\n\"" ) );
+ return;
+ }
+ //remove upgrade if carried
+ if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ {
+ BG_RemoveUpgradeFromInventory( upgrade, ent->client->ps.stats );
+
+ if( upgrade == UP_BATTPACK )
+ G_GiveClientMaxAmmo( ent, qtrue );
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( upgrade ), qfalse );
+ }
+ }
+ else if( !Q_stricmp( s, "weapons" ) )
+ {
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ //guard against selling the HBUILD weapons exploit
+ if( ( i == WP_HBUILD || i == WP_HBUILD2 ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) );
+ continue;
+ }
+
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) &&
+ BG_FindPurchasableForWeapon( i ) )
+ {
+ BG_RemoveWeaponFromInventory( i, ent->client->ps.stats );
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( i ), qfalse );
+ }
+
+ //if we have this weapon selected, force a new selection
+ if( i == ent->client->ps.weapon )
+ G_ForceWeaponChange( ent, WP_NONE );
+ }
+ }
+ else if( !Q_stricmp( s, "upgrades" ) )
+ {
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ //remove upgrade if carried
+ if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) &&
+ BG_FindPurchasableForUpgrade( i ) )
+ {
+ BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats );
+
+ if( i == UP_BATTPACK )
+ {
+ int j;
+
+ //remove energy
+ for( j = WP_NONE; j < WP_NUM_WEAPONS; j++ )
+ {
+ if( BG_InventoryContainsWeapon( j, ent->client->ps.stats ) &&
+ BG_FindUsesEnergyForWeapon( j ) &&
+ !BG_FindInfinteAmmoForWeapon( j ) )
+ {
+ // GH FIXME
+ ent->client->ps.ammo = 0;
+ ent->client->ps.clips = 0;
+ }
+ }
+ }
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( i ), qfalse );
+ }
+ }
+ }
+ else
+ trap_SendServerCommand( ent-g_entities, va( "print \"Unknown item\n\"" ) );
+
+ if( trap_Argc( ) >= 2 )
+ {
+ trap_Argv( 2, s, sizeof( s ) );
+
+ //retrigger the armoury menu
+ if( !Q_stricmp( s, "retrigger" ) )
+ ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES;
+ }
+
+ //update ClientInfo
+ ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );
+}
+
+
+/*
+=================
+Cmd_Build_f
+=================
+*/
+void Cmd_Build_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ buildable_t buildable;
+ float dist;
+ vec3_t origin;
+ pTeam_t team;
+
+ if( ent->client->pers.denyBuild )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Your building rights have been revoked\n\"" );
+ return;
+ }
+ if( ent->client->pers.paused )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You may not mark while paused\n\"" );
+ return;
+ }
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ buildable = BG_FindBuildNumForName( s );
+
+
+ if( g_suddenDeath.integer)
+ {
+ if( g_suddenDeathMode.integer == SDMODE_SELECTIVE )
+ {
+ if( !BG_FindReplaceableTestForBuildable( buildable ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"This building type cannot be rebuilt during Sudden Death\n\"" );
+ return;
+ }
+ if( G_BuildingExists( buildable ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You can only rebuild one of each type of rebuildable building during Sudden Death.\n\"" );
+ return;
+ }
+ }
+ else if( g_suddenDeathMode.integer == SDMODE_NO_BUILD )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Building is not allowed during Sudden Death\n\"" );
+ return;
+ }
+ }
+
+ team = ent->client->ps.stats[ STAT_PTEAM ];
+
+ if( buildable != BA_NONE &&
+ ( ( 1 << ent->client->ps.weapon ) & BG_FindBuildWeaponForBuildable( buildable ) ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) &&
+ BG_BuildableIsAllowed( buildable ) &&
+ ( ( team == PTE_ALIENS && BG_FindStagesForBuildable( buildable, g_alienStage.integer ) ) ||
+ ( team == PTE_HUMANS && BG_FindStagesForBuildable( buildable, g_humanStage.integer ) ) ) )
+ {
+ dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+
+ //these are the errors displayed when the builder first selects something to use
+ switch( G_CanBuild( ent, buildable, dist, origin ) )
+ {
+ case IBE_NONE:
+ case IBE_TNODEWARN:
+ case IBE_RPTWARN:
+ case IBE_RPTWARN2:
+ case IBE_SPWNWARN:
+ case IBE_NOROOM:
+ case IBE_NORMAL:
+ case IBE_HOVELEXIT:
+ ent->client->ps.stats[ STAT_BUILDABLE ] = ( buildable | SB_VALID_TOGGLEBIT );
+ break;
+
+ case IBE_NOASSERT:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT );
+ break;
+
+ case IBE_NOOVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND );
+ break;
+
+ case IBE_OVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND );
+ break;
+
+ case IBE_REACTOR:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR );
+ break;
+
+ case IBE_REPEATER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER );
+ break;
+
+ case IBE_NOPOWER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER );
+ break;
+
+ case IBE_NOCREEP:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );
+ break;
+
+ case IBE_NODCC:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ trap_SendServerCommand( ent-g_entities, va( "print \"Cannot build this item\n\"" ) );
+}
+
+
+/*
+=================
+Cmd_Boost_f
+=================
+*/
+void Cmd_Boost_f( gentity_t *ent )
+{
+ if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) &&
+ BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) )
+ return;
+
+ if( ent->client->pers.cmd.buttons & BUTTON_WALKING )
+ return;
+
+ if( ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) &&
+ ( ent->client->ps.stats[ STAT_STAMINA ] > 0 ) )
+ ent->client->ps.stats[ STAT_STATE ] |= SS_SPEEDBOOST;
+}
+
+/*
+=================
+Cmd_Protect_f
+=================
+*/
+void Cmd_Protect_f( gentity_t *ent )
+{
+ vec3_t forward, end;
+ trace_t tr;
+ gentity_t *traceEnt;
+
+ if( !ent->client->pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Only designated"
+ " builders can toggle structure protection.\n\"" );
+ return;
+ }
+
+ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
+ VectorMA( ent->client->ps.origin, 100, forward, end );
+
+ trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number,
+ MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) &&
+ ( traceEnt->biteam == ent->client->pers.teamSelection ) )
+ {
+ if( traceEnt->s.eFlags & EF_DBUILDER )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Structure protection removed\n\"" );
+ traceEnt->s.eFlags &= ~EF_DBUILDER;
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Structure protection applied\n\"" );
+ traceEnt->s.eFlags |= EF_DBUILDER;
+
+ // adding protection turns off deconstruction mark
+ traceEnt->deconstruct = qfalse;
+ }
+ }
+}
+
+ /*
+ =================
+ Cmd_Resign_f
+ =================
+ */
+ void Cmd_Resign_f( gentity_t *ent )
+ {
+ if( !ent->client->pers.designatedBuilder )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You are not a designated builder\n\"" );
+ return;
+ }
+
+ ent->client->pers.designatedBuilder = qfalse;
+ trap_SendServerCommand( -1, va(
+ "print \"%s" S_COLOR_WHITE " has resigned\n\"",
+ ent->client->pers.netname ) );
+ G_CheckDBProtection( );
+ }
+
+
+
+/*
+=================
+Cmd_Reload_f
+=================
+*/
+void Cmd_Reload_f( gentity_t *ent )
+{
+ if( ( ent->client->ps.weapon >= WP_ABUILD ) &&
+ ( ent->client->ps.weapon <= WP_HBUILD ) )
+ {
+ if( ent->client->pers.designatedBuilder )
+ Cmd_Protect_f( ent );
+ else
+ Cmd_Mark_f( ent );
+ }
+ else if( ent->client->ps.weaponstate != WEAPON_RELOADING )
+ ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD;
+}
+
+
+/*
+=================
+Cmd_MyStats_f
+=================
+*/
+void Cmd_MyStats_f( gentity_t *ent )
+{
+
+ if(!ent) return;
+
+
+ if( !level.intermissiontime && ent->client->pers.statscounters.timeLastViewed && (level.time - ent->client->pers.statscounters.timeLastViewed) <60000 )
+ {
+ ADMP( "You may only check your stats once per minute and during intermission.\n");
+ return;
+ }
+
+ if( !g_myStats.integer )
+ {
+ ADMP( "myStats has been disabled\n");
+ return;
+ }
+
+ ADMP( G_statsString( &ent->client->pers.statscounters, &ent->client->pers.teamSelection ) );
+ ent->client->pers.statscounters.timeLastViewed = level.time;
+
+ return;
+}
+
+char *G_statsString( statsCounters_t *sc, pTeam_t *pt )
+{
+ char *s;
+
+ int percentNearBase=0;
+ int percentJetpackWallwalk=0;
+ int percentHeadshots=0;
+ double avgTimeAlive=0;
+ int avgTimeAliveMins = 0;
+ int avgTimeAliveSecs = 0;
+
+ if( sc->timealive )
+ percentNearBase = (int)(100 * (float) sc->timeinbase / ((float) (sc->timealive ) ) );
+
+ if( sc->timealive && sc->deaths )
+ {
+ avgTimeAlive = sc->timealive / sc->deaths;
+ }
+
+ avgTimeAliveMins = (int) (avgTimeAlive / 60.0f);
+ avgTimeAliveSecs = (int) (avgTimeAlive - (60.0f * avgTimeAliveMins));
+
+ if( *pt == PTE_ALIENS )
+ {
+ if( sc->dretchbasytime > 0 )
+ percentJetpackWallwalk = (int)(100 * (float) sc->jetpackusewallwalkusetime / ((float) ( sc->dretchbasytime) ) );
+
+ if( sc->hitslocational )
+ percentHeadshots = (int)(100 * (float) sc->headshots / ((float) (sc->hitslocational) ) );
+
+ s = va( "^3Kills:^7 %3i ^3StructKills:^7 %3i ^3Assists:^7 %3i^7 ^3Poisons:^7 %3i ^3Headshots:^7 %3i (%3i)\n^3Deaths:^7 %3i ^3Feeds:^7 %3i ^3Suicides:^7 %3i ^3TKs:^7 %3i ^3Avg Lifespan:^7 %4d:%02d\n^3Damage to:^7 ^3Enemies:^7 %5i ^3Structs:^7 %5i ^3Friendlies:^7 %3i \n^3Structs Built:^7 %3i ^3Time Near Base:^7 %3i ^3Time wallwalking:^7 %3i\n",
+ sc->kills,
+ sc->structskilled,
+ sc->assists,
+ sc->repairspoisons,
+ sc->headshots,
+ percentHeadshots,
+ sc->deaths,
+ sc->feeds,
+ sc->suicides,
+ sc->teamkills,
+ avgTimeAliveMins,
+ avgTimeAliveSecs,
+ sc->dmgdone,
+ sc->structdmgdone,
+ sc->ffdmgdone,
+ sc->structsbuilt,
+ percentNearBase,
+ percentJetpackWallwalk
+ );
+ }
+ else if( *pt == PTE_HUMANS )
+ {
+ if( sc->timealive )
+ percentJetpackWallwalk = (int)(100 * (float) sc->jetpackusewallwalkusetime / ((float) ( sc->timealive ) ) );
+ s = va( "^3Kills:^7 %3i ^3StructKills:^7 %3i ^3Assists:^7 %3i \n^3Deaths:^7 %3i ^3Feeds:^7 %3i ^3Suicides:^7 %3i ^3TKs:^7 %3i ^3Avg Lifespan:^7 %4d:%02d\n^3Damage to:^7 ^3Enemies:^7 %5i ^3Structs:^7 %5i ^3Friendlies:^7 %3i \n^3Structs Built:^7 %3i ^3Repairs:^7 %4i ^3Time Near Base:^7 %3i ^3Time Jetpacking:^7 %3i\n",
+ sc->kills,
+ sc->structskilled,
+ sc->assists,
+ sc->deaths,
+ sc->feeds,
+ sc->suicides,
+ sc->teamkills,
+ avgTimeAliveMins,
+ avgTimeAliveSecs,
+ sc->dmgdone,
+ sc->structdmgdone,
+ sc->ffdmgdone,
+ sc->structsbuilt,
+ sc->repairspoisons,
+ percentNearBase,
+ percentJetpackWallwalk
+ );
+ }
+ else s="No stats available\n";
+
+ return s;
+}
+
+ /*
+ =================
+ Cmd_AllStats_f
+ =================
+ */
+ void Cmd_AllStats_f( gentity_t *ent )
+ {
+ int i;
+ int NextViewTime;
+ int NumResults = 0;
+ int Teamcolor = 3;
+ gentity_t *tmpent;
+
+ //check if ent exists
+ if(!ent) return;
+
+ NextViewTime = ent->client->pers.statscounters.AllstatstimeLastViewed + g_AllStatsTime.integer * 1000;
+ //check if you can use the cmd at this time
+ if( !level.intermissiontime && level.time < NextViewTime)
+ {
+ ADMP( va("You may only check your stats every %i Seconds and during intermission. Next valid time is %d:%02d\n",( g_AllStatsTime.integer ) ? ( g_AllStatsTime.integer ) : 60, ( NextViewTime / 60000 ), ( NextViewTime / 1000 ) % 60 ) );
+ return;
+ }
+ //see if allstats is enabled
+ if( !g_AllStats.integer )
+ {
+ ADMP( "AllStats has been disabled\n");
+ return;
+ }
+ ADMP("^3K^2=^7Kills ^3A^2=^7Assists ^3SK^2=^7StructKills\n^3D^2=^7Deaths ^3F^2=^7Feeds ^3S^2=^7Suicides ^3TK^2=^7Teamkills\n^3DD^2=^7Damage done ^3TDD^2=^7Team Damage done\n^3SB^2=^7Structs Built\n\n" );
+ //display a header describing the data
+ ADMP( "^3 #| K A SK| D F S TK| DD TDD| SB| Name\n" );
+
+ //loop through the clients that are connected
+ for( i = 0; i < level.numConnectedClients; i++ )
+ {
+ //assign a tempent 4 the hell of it
+ tmpent = &g_entities[ level.sortedClients[ i ] ];
+
+ //check for what mode we are working in and display relevent data
+ if( g_AllStats.integer == 1 )
+ {
+ //check if client is connected and on same team
+ if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && tmpent->client->pers.teamSelection == ent->client->pers.teamSelection && tmpent->client->pers.teamSelection != PTE_NONE )
+ {
+ NumResults++;
+ if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1;
+ if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5;
+ ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n",
+ Teamcolor,
+ NumResults,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0,
+ ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0,
+ ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0,
+ ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0,
+ ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0,
+ ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0,
+ ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0,
+ ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) );
+ }
+ }
+ else if( g_AllStats.integer == 2 )
+ {
+ //check if client is connected and has some stats or atleast is on a team
+ if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && ( tmpent->client->pers.teamSelection != PTE_NONE ) )
+ {
+ NumResults++;
+ if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1;
+ if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5;
+ ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n",
+ Teamcolor,
+ NumResults,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0,
+ ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0,
+ ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0,
+ ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0,
+ ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0,
+ ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0,
+ ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0,
+ Teamcolor,
+ ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0,
+ ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) );
+ }
+ }
+ }
+ if( NumResults == 0 ) {
+ ADMP( " ^3EMPTY!\n" );
+ } else {
+ ADMP( va( "^7 %i Players found!\n", NumResults ) );
+ }
+ //update time last viewed
+
+ ent->client->pers.statscounters.AllstatstimeLastViewed = level.time;
+ return;
+}
+
+/*
+=================
+Cmd_TeamStatus_f
+=================
+*/
+void Cmd_TeamStatus_f( gentity_t *ent )
+{
+ char multiple[ 12 ];
+ int builders = 0;
+ int arm = 0, mediboost = 0;
+ int omrccount = 0, omrchealth = 0;
+ qboolean omrcbuild = qfalse;
+ gentity_t *tmp;
+ int i;
+
+ if( !g_teamStatus.integer )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "print \"teamstatus is disabled.\n\"" );
+ return;
+ }
+
+ if( G_IsMuted( ent->client ) )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "print \"You are muted and cannot use message commands.\n\"" );
+ return;
+ }
+
+ if( ent->client->pers.lastTeamStatus &&
+ ( level.time - ent->client->pers.lastTeamStatus) < g_teamStatus.integer * 1000 )
+ {
+ ADMP( va("You may only check your team's status once every %i seconds.\n",
+ g_teamStatus.integer ));
+ return;
+ }
+
+ ent->client->pers.lastTeamStatus = level.time;
+
+ tmp = &g_entities[ 0 ];
+ for ( i = 0; i < level.num_entities; i++, tmp++ )
+ {
+ if( i < MAX_CLIENTS )
+ {
+ if( tmp->client &&
+ tmp->client->pers.connected == CON_CONNECTED &&
+ tmp->client->pers.teamSelection == ent->client->pers.teamSelection &&
+ tmp->health > 0 &&
+ ( tmp->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ||
+ tmp->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ||
+ BG_InventoryContainsWeapon( WP_HBUILD, tmp->client->ps.stats ) ||
+ BG_InventoryContainsWeapon( WP_HBUILD2, tmp->client->ps.stats ) ) )
+ builders++;
+ continue;
+ }
+
+ if( tmp->s.eType == ET_BUILDABLE )
+ {
+ if( tmp->biteam != ent->client->pers.teamSelection ||
+ tmp->health <= 0 )
+ continue;
+
+ switch( tmp->s.modelindex )
+ {
+ case BA_H_REACTOR:
+ case BA_A_OVERMIND:
+ omrccount++;
+ if( tmp->health > omrchealth )
+ omrchealth = tmp->health;
+ if( !omrcbuild )
+ omrcbuild = tmp->spawned;
+ break;
+ case BA_H_ARMOURY:
+ arm++;
+ break;
+ case BA_H_MEDISTAT:
+ case BA_A_BOOSTER:
+ mediboost++;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if( omrccount > 1 )
+ Com_sprintf( multiple, sizeof( multiple ), "^7[x%d]", omrccount );
+ else
+ multiple[ 0 ] = '\0';
+
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ {
+ G_Say( ent, NULL, SAY_TEAM,
+ va( "^3OM: %s(%d)%s ^3Spawns: ^5%d ^3Builders: ^5%d ^3Boosters: ^5%d^7" ,
+ ( !omrccount ) ? "^1Down" : ( omrcbuild ) ? "^2Up" : "^5Building",
+ omrchealth * 100 / OVERMIND_HEALTH,
+ multiple,
+ level.numAlienSpawns,
+ builders,
+ mediboost ) );
+ }
+ else
+ {
+ G_Say( ent, NULL, SAY_TEAM,
+ va( "^3RC: %s(%d)%s ^3Spawns: ^5%d ^3Builders: ^5%d ^3Armouries: ^5%d ^3Medistations: ^5%d^7" ,
+ ( !omrccount ) ? "^1Down" : ( omrcbuild ) ? "^2Up" : "^5Building",
+ omrchealth * 100 / REACTOR_HEALTH,
+ multiple,
+ level.numHumanSpawns,
+ builders,
+ arm, mediboost ) );
+ }
+}
+
+/*
+=================
+G_StopFromFollowing
+
+stops any other clients from following this one
+called when a player leaves a team or dies
+=================
+*/
+void G_StopFromFollowing( gentity_t *ent )
+{
+ int i;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW &&
+ level.clients[ i ].sess.spectatorClient == ent-g_entities )
+ {
+ if( !G_FollowNewClient( &g_entities[ i ], 1 ) )
+ G_StopFollowing( &g_entities[ i ] );
+ }
+ }
+}
+
+/*
+=================
+G_StopFollowing
+
+If the client being followed leaves the game, or you just want to drop
+to free floating spectator mode
+=================
+*/
+void G_StopFollowing( gentity_t *ent )
+{
+ ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR;
+ ent->client->sess.sessionTeam = TEAM_SPECTATOR;
+ ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection;
+
+ if( ent->client->pers.teamSelection == PTE_NONE )
+ {
+ ent->client->sess.spectatorState = SPECTATOR_FREE;
+ ent->client->ps.pm_type = PM_SPECTATOR;
+ ent->client->ps.stats[ STAT_HEALTH ] = 100; // hacky server-side fix to prevent cgame from viewlocking a freespec
+ }
+ else
+ {
+ vec3_t spawn_origin, spawn_angles;
+
+ ent->client->sess.spectatorState = SPECTATOR_LOCKED;
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
+ else if( ent->client->pers.teamSelection == PTE_HUMANS )
+ G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
+ G_SetOrigin( ent, spawn_origin );
+ VectorCopy( spawn_origin, ent->client->ps.origin );
+ G_SetClientViewAngle( ent, spawn_angles );
+ }
+ ent->client->sess.spectatorClient = -1;
+ ent->client->ps.pm_flags &= ~PMF_FOLLOW;
+
+ // Prevent spawning with bsuit in rare case
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) )
+ BG_RemoveUpgradeFromInventory( UP_BATTLESUIT, ent->client->ps.stats );
+
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
+ ent->client->ps.eFlags &= ~EF_WALLCLIMB;
+ ent->client->ps.viewangles[ PITCH ] = 0.0f;
+
+ ent->client->ps.clientNum = ent - g_entities;
+
+ CalculateRanks( );
+}
+
+/*
+=================
+G_FollowNewClient
+
+This was a really nice, elegant function. Then I fucked it up.
+=================
+*/
+qboolean G_FollowNewClient( gentity_t *ent, int dir )
+{
+ int clientnum = ent->client->sess.spectatorClient;
+ int original = clientnum;
+ qboolean selectAny = qfalse;
+
+ if( dir > 1 )
+ dir = 1;
+ else if( dir < -1 )
+ dir = -1;
+ else if( dir == 0 )
+ return qtrue;
+
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ return qfalse;
+
+ // select any if no target exists
+ if( clientnum < 0 || clientnum >= level.maxclients )
+ {
+ clientnum = original = 0;
+ selectAny = qtrue;
+ }
+
+ do
+ {
+ clientnum += dir;
+
+ if( clientnum >= level.maxclients )
+ clientnum = 0;
+
+ if( clientnum < 0 )
+ clientnum = level.maxclients - 1;
+
+ // avoid selecting existing follow target
+ if( clientnum == original && !selectAny )
+ continue; //effectively break;
+
+ // can't follow self
+ if( &level.clients[ clientnum ] == ent->client )
+ continue;
+
+ // can only follow connected clients
+ if( level.clients[ clientnum ].pers.connected != CON_CONNECTED )
+ continue;
+
+ // can't follow another spectator
+ if( level.clients[ clientnum ].pers.teamSelection == PTE_NONE )
+ continue;
+
+ // can only follow teammates when dead and on a team
+ if( ent->client->pers.teamSelection != PTE_NONE &&
+ ( level.clients[ clientnum ].pers.teamSelection !=
+ ent->client->pers.teamSelection ) )
+ continue;
+
+ // cannot follow a teammate who is following you
+ if( level.clients[ clientnum ].sess.spectatorState == SPECTATOR_FOLLOW &&
+ ( level.clients[ clientnum ].sess.spectatorClient == ent->s.number ) )
+ continue;
+
+ // this is good, we can use it
+ ent->client->sess.spectatorClient = clientnum;
+ ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
+ return qtrue;
+
+ } while( clientnum != original );
+
+ return qfalse;
+}
+
+/*
+=================
+G_ToggleFollow
+=================
+*/
+void G_ToggleFollow( gentity_t *ent )
+{
+ if( level.mapRotationVoteTime )
+ {
+ G_IntermissionMapVoteCommand( ent, qfalse, qtrue );
+ return;
+ }
+
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+ else
+ G_FollowNewClient( ent, 1 );
+}
+
+/*
+=================
+Cmd_Follow_f
+=================
+*/
+void Cmd_Follow_f( gentity_t *ent )
+{
+ int i;
+ int pids[ MAX_CLIENTS ];
+ char arg[ MAX_TOKEN_CHARS ];
+
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ {
+ trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow unless you are dead or on the spectators.\n\"" );
+ return;
+ }
+ if( ent->client->pers.paused )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You may not build while paused\n\"" );
+ return;
+ }
+
+ if( trap_Argc( ) != 2 )
+ {
+ G_ToggleFollow( ent );
+ }
+ else
+ {
+ trap_Argv( 1, arg, sizeof( arg ) );
+ if( G_ClientNumbersFromString( arg, pids ) == 1 )
+ {
+ i = pids[ 0 ];
+ }
+ else
+ {
+ i = G_ClientNumberFromString( ent, arg );
+
+ if( i == -1 )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "print \"follow: invalid player\n\"" );
+ return;
+ }
+ }
+
+ // can't follow self
+ if( &level.clients[ i ] == ent->client )
+ {
+ trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow yourself.\n\"" );
+ return;
+ }
+
+ // can't follow another spectator
+ if( level.clients[ i ].pers.teamSelection == PTE_NONE)
+ {
+ trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow another spectator.\n\"" );
+ return;
+ }
+
+ // can only follow teammates when dead and on a team
+ if( ent->client->pers.teamSelection != PTE_NONE &&
+ ( level.clients[ i ].pers.teamSelection !=
+ ent->client->pers.teamSelection ) )
+ {
+ trap_SendServerCommand( ent - g_entities, "print \"follow: You can only follow teammates, and only when you are dead.\n\"" );
+ return;
+ }
+
+ ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
+ ent->client->sess.spectatorClient = i;
+ }
+}
+
+/*
+=================
+Cmd_FollowCycle_f
+=================
+*/
+void Cmd_FollowCycle_f( gentity_t *ent )
+{
+ char args[ 11 ];
+ int dir = 1;
+
+ trap_Argv( 0, args, sizeof( args ) );
+ if( Q_stricmp( args, "followprev" ) == 0 )
+ dir = -1;
+
+ // won't work unless spectating
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ return;
+ if( ent->client->sess.spectatorState == SPECTATOR_NOT )
+ return;
+ G_FollowNewClient( ent, dir );
+}
+
+/*
+=================
+Cmd_PTRCVerify_f
+
+Check a PTR code is valid
+=================
+*/
+void Cmd_PTRCVerify_f( gentity_t *ent )
+{
+ connectionRecord_t *connection;
+ char s[ MAX_TOKEN_CHARS ] = { 0 };
+ int code;
+
+ if( ent->client->pers.connection )
+ return;
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ if( !strlen( s ) )
+ return;
+
+ code = atoi( s );
+
+ connection = G_FindConnectionForCode( code );
+ if( connection && connection->clientNum == -1 )
+ {
+ // valid code
+ if( connection->clientTeam != PTE_NONE )
+ trap_SendServerCommand( ent->client->ps.clientNum, "ptrcconfirm" );
+
+ // restore mapping
+ ent->client->pers.connection = connection;
+ connection->clientNum = ent->client->ps.clientNum;
+ }
+ else
+ {
+ // invalid code -- generate a new one
+ connection = G_GenerateNewConnection( ent->client );
+
+ if( connection )
+ {
+ trap_SendServerCommand( ent->client->ps.clientNum,
+ va( "ptrcissue %d", connection->ptrCode ) );
+ }
+ }
+}
+
+/*
+=================
+Cmd_PTRCRestore_f
+
+Restore against a PTR code
+=================
+*/
+void Cmd_PTRCRestore_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ] = { 0 };
+ int code;
+ connectionRecord_t *connection;
+
+ if( ent->client->pers.joinedATeam )
+ {
+ trap_SendServerCommand( ent - g_entities,
+ "print \"You cannot use a PTR code after joining a team\n\"" );
+ return;
+ }
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ if( !strlen( s ) )
+ return;
+
+ code = atoi( s );
+
+ connection = ent->client->pers.connection;
+ if( connection && connection->ptrCode == code )
+ {
+ // Set the correct team
+ if( !( ent->client->pers.specExpires > level.time ) )
+ {
+ // Check if the alien team is full
+ if( connection->clientTeam == PTE_ALIENS &&
+ !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) &&
+ g_teamForceBalance.integer &&
+ level.numAlienClients > level.numHumanClients )
+ {
+ G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL );
+ }
+ // Check if the human team is full
+ else if( connection->clientTeam == PTE_HUMANS &&
+ !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) &&
+ g_teamForceBalance.integer &&
+ level.numHumanClients > level.numAlienClients )
+ {
+ G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL );
+ }
+ else
+ {
+ G_ChangeTeam( ent, connection->clientTeam );
+ }
+ }
+
+ // set the correct credit etc.
+ ent->client->ps.persistant[ PERS_CREDIT ] = 0;
+ G_AddCreditToClient( ent->client, connection->clientCredit, qtrue );
+ ent->client->pers.score = connection->clientScore;
+ ent->client->pers.enterTime = connection->clientEnterTime;
+ }
+ else
+ {
+ trap_SendServerCommand( ent - g_entities,
+ va( "print \"\"%d\" is not a valid PTR code\n\"", code ) );
+ }
+}
+
+static void Cmd_Ignore_f( gentity_t *ent )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ];
+ char cmd[ 9 ];
+ int matches = 0;
+ int i;
+ qboolean ignore = qfalse;
+
+ trap_Argv( 0, cmd, sizeof( cmd ) );
+ if( Q_stricmp( cmd, "ignore" ) == 0 )
+ ignore = qtrue;
+
+ if( trap_Argc() < 2 )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"
+ "%s: usage \\%s [clientNum | partial name match]\n\"", cmd, cmd ) );
+ return;
+ }
+
+ Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) );
+ matches = G_ClientNumbersFromString( name, pids );
+ if( matches < 1 )
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"
+ "%s: no clients match the name '%s'\n\"", cmd, name ) );
+ return;
+ }
+
+ for( i = 0; i < matches; i++ )
+ {
+ if( ignore )
+ {
+ if( !BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) )
+ {
+ BG_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] );
+ ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );
+ trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"
+ "ignore: added %s^7 to your ignore list\n\"",
+ level.clients[ pids[ i ] ].pers.netname ) );
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"
+ "ignore: %s^7 is already on your ignore list\n\"",
+ level.clients[ pids[ i ] ].pers.netname ) );
+ }
+ }
+ else
+ {
+ if( BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) )
+ {
+ BG_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] );
+ ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );
+ trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"
+ "unignore: removed %s^7 from your ignore list\n\"",
+ level.clients[ pids[ i ] ].pers.netname ) );
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"
+ "unignore: %s^7 is not on your ignore list\n\"",
+ level.clients[ pids[ i ] ].pers.netname ) );
+ }
+ }
+ }
+}
+
+ /*
+ =================
+ Cmd_Share_f
+ =================
+ */
+ void Cmd_Share_f( gentity_t *ent )
+ {
+ int i, clientNum = 0, creds = 0, skipargs = 0;
+ int clientNums[ MAX_CLIENTS ] = { -1 };
+ char cmd[ 12 ];
+ char arg1[ MAX_STRING_TOKENS ];
+ char arg2[ MAX_STRING_TOKENS ];
+ pTeam_t team;
+
+ if( !ent || !ent->client || ( ent->client->pers.teamSelection == PTE_NONE ) )
+ {
+ return;
+ }
+
+ if( !g_allowShare.integer )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Share has been disabled.\n\"" );
+ return;
+ }
+
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
+ return;
+ }
+
+ team = ent->client->pers.teamSelection;
+
+ G_SayArgv( 0, cmd, sizeof( cmd ) );
+ if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) )
+ {
+ skipargs = 1;
+ G_SayArgv( 1, cmd, sizeof( cmd ) );
+ }
+
+ // target player name is in arg1
+ G_SayArgv( 1+skipargs, arg1, sizeof( arg1 ) );
+ // amount to be shared is in arg2
+ G_SayArgv( 2+skipargs, arg2, sizeof( arg2 ) );
+
+ if( arg1[0] && !strchr( arg1, ';' ) && Q_stricmp( arg1, "target_in_aim" ) )
+ {
+ //check arg1 is a number
+ for( i = 0; arg1[ i ]; i++ )
+ {
+ if( arg1[ i ] < '0' || arg1[ i ] > '9' )
+ {
+ clientNum = -1;
+ break;
+ }
+ }
+
+ if( clientNum >= 0 )
+ {
+ clientNum = atoi( arg1 );
+ }
+ else if( G_ClientNumbersFromString( arg1, clientNums ) == 1 )
+ {
+ // there was one partial name match
+ clientNum = clientNums[ 0 ];
+ }
+ else
+ {
+ // look for an exact name match before bailing out
+ clientNum = G_ClientNumberFromString( ent, arg1 );
+ if( clientNum == -1 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"share: invalid player name specified.\n\"" );
+ return;
+ }
+ }
+ }
+ else // arg1 not set
+ {
+ vec3_t forward, end;
+ trace_t tr;
+ gentity_t *traceEnt;
+
+ // trace a teammate
+ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
+ VectorMA( ent->client->ps.origin, 8192 * 16, forward, end );
+
+ trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( tr.fraction < 1.0f && traceEnt->client &&
+ ( traceEnt->client->pers.teamSelection == team ) )
+ {
+ clientNum = traceEnt - g_entities;
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"share: aim at a teammate to share %s.\n\"",
+ ( team == PTE_HUMANS ) ? "credits" : "evolvepoints" ) );
+ return;
+ }
+ }
+
+ // verify target player team
+ if( ( clientNum < 0 ) || ( clientNum >= level.maxclients ) ||
+ ( level.clients[ clientNum ].pers.teamSelection != team ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"share: not a valid player of your team.\n\"" );
+ return;
+ }
+
+ if( !arg2[0] || strchr( arg2, ';' ) )
+ {
+ // default credit count
+ if( team == PTE_HUMANS )
+ {
+ creds = FREEKILL_HUMAN;
+ }
+ else if( team == PTE_ALIENS )
+ {
+ creds = FREEKILL_ALIEN;
+ }
+ }
+ else
+ {
+ //check arg2 is a number
+ for( i = 0; arg2[ i ]; i++ )
+ {
+ if( arg2[ i ] < '0' || arg2[ i ] > '9' )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"usage: share [name|slot#] [amount]\n\"" );
+ return;
+ }
+ }
+
+ // credit count from parameter
+ creds = atoi( arg2 );
+ }
+
+ // player specified "0" to transfer
+ if( creds <= 0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Ooh, you are a generous one, indeed!\n\"" );
+ return;
+ }
+
+ // transfer only credits the player really has
+ if( creds > ent->client->pers.credit )
+ {
+ creds = ent->client->pers.credit;
+ }
+
+ // player has no credits
+ if( creds <= 0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Earn some first, lazy gal!\n\"" );
+ return;
+ }
+
+ // allow transfers only up to the credit/evo limit
+ if( ( team == PTE_HUMANS ) &&
+ ( creds > HUMAN_MAX_CREDITS - level.clients[ clientNum ].pers.credit ) )
+ {
+ creds = HUMAN_MAX_CREDITS - level.clients[ clientNum ].pers.credit;
+ }
+ else if( ( team == PTE_ALIENS ) &&
+ ( creds > ALIEN_MAX_KILLS - level.clients[ clientNum ].pers.credit ) )
+ {
+ creds = ALIEN_MAX_KILLS - level.clients[ clientNum ].pers.credit;
+ }
+
+ // target cannot take any more credits
+ if( creds <= 0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"share: player cannot receive any more %s.\n\"",
+ ( team == PTE_HUMANS ) ? "credits" : "evolvepoints" ) );
+ return;
+ }
+
+ // transfer credits
+ G_AddCreditToClient( ent->client, -creds, qfalse );
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"share: transferred %d %s to %s^7.\n\"", creds,
+ ( team == PTE_HUMANS ) ? "credits" : "evolvepoints",
+ level.clients[ clientNum ].pers.netname ) );
+ G_AddCreditToClient( &(level.clients[ clientNum ]), creds, qtrue );
+ trap_SendServerCommand( clientNum,
+ va( "print \"You have received %d %s from %s^7.\n\"", creds,
+ ( team == PTE_HUMANS ) ? "credits" : "evolvepoints",
+ ent->client->pers.netname ) );
+
+ G_LogPrintf( "Share: %i %i %i %d: %s^7 transferred %d%s to %s^7\n",
+ ent->client->ps.clientNum,
+ clientNum,
+ team,
+ creds,
+ ent->client->pers.netname,
+ creds,
+ ( team == PTE_HUMANS ) ? "c" : "e",
+ level.clients[ clientNum ].pers.netname );
+ }
+
+ /*
+ =================
+ Cmd_Donate_f
+
+ Alms for the poor
+ =================
+ */
+ void Cmd_Donate_f( gentity_t *ent ) {
+ char s[ MAX_TOKEN_CHARS ] = "", *type = "evo(s)";
+ int i, value, divisor, portion, new_credits, total=0,
+ max = ALIEN_MAX_KILLS, *amounts, *totals;
+ qboolean donated = qtrue;
+
+ if( !ent->client ) return;
+
+ if( !g_allowShare.integer )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Donate has been disabled.\n\"" );
+ return;
+ }
+
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
+ return;
+ }
+
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ divisor = level.numAlienClients-1;
+ else if( ent->client->pers.teamSelection == PTE_HUMANS ) {
+ divisor = level.numHumanClients-1;
+ max = HUMAN_MAX_CREDITS;
+ type = "credit(s)";
+ } else {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"donate: spectators cannot be so gracious\n\"" ) );
+ return;
+ }
+
+ if( divisor < 1 ) {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"donate: get yourself some teammates first\n\"" );
+ return;
+ }
+
+ trap_Argv( 1, s, sizeof( s ) );
+ value = atoi(s);
+ if( value <= 0 ) {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"donate: very funny\n\"" );
+ return;
+ }
+ if( value > ent->client->pers.credit)
+ value = ent->client->pers.credit;
+
+ // allocate memory for distribution amounts
+ amounts = G_Alloc( level.maxclients * sizeof( int ) );
+ totals = G_Alloc( level.maxclients * sizeof( int ) );
+ for( i = 0; i < level.maxclients; i++ ) {
+ amounts[ i ] = 0;
+ totals[ i ] = 0;
+ }
+
+ // determine donation amounts for each client
+ total = value;
+ while( donated && value ) {
+ donated = qfalse;
+ portion = value / divisor;
+ if( portion < 1 ) portion = 1;
+ for( i = 0; i < level.maxclients; i++ )
+ if( level.clients[ i ].pers.connected == CON_CONNECTED &&
+ ent->client != level.clients + i &&
+ level.clients[ i ].pers.teamSelection ==
+ ent->client->pers.teamSelection &&
+ level.clients[ i ].pers.credit < max ) {
+ new_credits = level.clients[ i ].pers.credit + portion;
+ amounts[ i ] = portion;
+ totals[ i ] += portion;
+ if( new_credits > max ) {
+ amounts[ i ] -= new_credits - max;
+ totals[ i ] -= new_credits - max;
+ new_credits = max;
+ }
+ if( amounts[ i ] ) {
+ G_AddCreditToClient( &(level.clients[ i ]), amounts[ i ], qtrue );
+ donated = qtrue;
+ value -= amounts[ i ];
+ if( value < portion ) break;
+ }
+ }
+ }
+
+ // transfer funds
+ G_AddCreditToClient( ent->client, value - total, qtrue );
+ for( i = 0; i < level.maxclients; i++ )
+ if( totals[ i ] ) {
+ trap_SendServerCommand( i,
+ va( "print \"%s^7 donated %d %s to you, don't forget to say 'thank you'!\n\"",
+ ent->client->pers.netname, totals[ i ], type ) );
+ }
+
+ G_Free( amounts );
+ G_Free( totals );
+
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Donated %d %s to the cause.\n\"",
+ total-value, type ) );
+ }
+
+commands_t cmds[ ] = {
+ // normal commands
+ { "team", 0, Cmd_Team_f },
+ { "vote", CMD_INTERMISSION, Cmd_Vote_f },
+ { "ignore", 0, Cmd_Ignore_f },
+ { "unignore", 0, Cmd_Ignore_f },
+
+ // communication commands
+ { "tell", CMD_MESSAGE, Cmd_Tell_f },
+ { "callvote", CMD_MESSAGE, Cmd_CallVote_f },
+ { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallTeamVote_f },
+ { "say_area", CMD_MESSAGE|CMD_TEAM, Cmd_SayArea_f },
+ // can be used even during intermission
+ { "say", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f },
+ { "say_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f },
+ { "say_admins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f },
+ { "say_hadmins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f },
+ { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f },
+ { "m", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage },
+ { "mt", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage },
+ { "me", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f },
+ { "me_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f },
+
+ { "score", CMD_INTERMISSION, ScoreboardMessage },
+ { "mystats", CMD_TEAM|CMD_INTERMISSION, Cmd_MyStats_f },
+ { "allstats", 0|CMD_INTERMISSION, Cmd_AllStats_f },
+ { "teamstatus", CMD_TEAM, Cmd_TeamStatus_f },
+
+ // cheats
+ { "give", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Give_f },
+ { "god", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_God_f },
+ { "notarget", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Notarget_f },
+ { "noclip", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Noclip_f },
+ { "levelshot", CMD_CHEAT, Cmd_LevelShot_f },
+ { "setviewpos", CMD_CHEAT, Cmd_SetViewpos_f },
+ { "destroy", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Destroy_f },
+
+ { "kill", CMD_TEAM|CMD_LIVING, Cmd_Kill_f },
+
+ // game commands
+ { "ptrcverify", CMD_NOTEAM, Cmd_PTRCVerify_f },
+ { "ptrcrestore", CMD_NOTEAM, Cmd_PTRCRestore_f },
+
+ { "follow", 0, Cmd_Follow_f },
+ { "follownext", 0, Cmd_FollowCycle_f },
+ { "followprev", 0, Cmd_FollowCycle_f },
+
+ { "where", CMD_TEAM, Cmd_Where_f },
+ { "teamvote", CMD_TEAM, Cmd_TeamVote_f },
+ { "class", CMD_TEAM, Cmd_Class_f },
+
+ { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f },
+ { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f },
+ { "mark", CMD_TEAM|CMD_LIVING, Cmd_Mark_f },
+
+ { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f },
+ { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f },
+ { "itemact", CMD_HUMAN|CMD_LIVING, Cmd_ActivateItem_f },
+ { "itemdeact", CMD_HUMAN|CMD_LIVING, Cmd_DeActivateItem_f },
+ { "itemtoggle", CMD_HUMAN|CMD_LIVING, Cmd_ToggleItem_f },
+ { "reload", CMD_TEAM|CMD_LIVING, Cmd_Reload_f },
+ { "boost", 0, Cmd_Boost_f },
+ { "share", CMD_TEAM, Cmd_Share_f },
+ { "donate", CMD_TEAM, Cmd_Donate_f },
+ { "protect", CMD_TEAM|CMD_LIVING, Cmd_Protect_f },
+ { "resign", CMD_TEAM, Cmd_Resign_f },
+ { "builder", 0, Cmd_Builder_f }
+};
+static int numCmds = sizeof( cmds ) / sizeof( cmds[ 0 ] );
+
+/*
+=================
+ClientCommand
+=================
+*/
+void ClientCommand( int clientNum )
+{
+ gentity_t *ent;
+ char cmd[ MAX_TOKEN_CHARS ];
+ int i;
+
+ ent = g_entities + clientNum;
+ if( !ent->client )
+ return; // not fully in game yet
+
+ trap_Argv( 0, cmd, sizeof( cmd ) );
+
+ for( i = 0; i < numCmds; i++ )
+ {
+ if( Q_stricmp( cmd, cmds[ i ].cmdName ) == 0 )
+ break;
+ }
+
+ if( i == numCmds )
+ {
+ if( !G_admin_cmd_check( ent, qfalse ) )
+ trap_SendServerCommand( clientNum,
+ va( "print \"Unknown command %s\n\"", cmd ) );
+ return;
+ }
+
+ // do tests here to reduce the amount of repeated code
+
+ if( !( cmds[ i ].cmdFlags & CMD_INTERMISSION ) && ( level.intermissiontime || level.paused ) )
+ return;
+
+ if( cmds[ i ].cmdFlags & CMD_CHEAT && !g_cheats.integer )
+ {
+ trap_SendServerCommand( clientNum,
+ "print \"Cheats are not enabled on this server\n\"" );
+ return;
+ }
+
+ if( cmds[ i ].cmdFlags & CMD_MESSAGE && G_IsMuted( ent->client ) )
+ {
+ trap_SendServerCommand( clientNum,
+ "print \"You are muted and cannot use message commands.\n\"" );
+ return;
+ }
+
+ if( cmds[ i ].cmdFlags & CMD_TEAM &&
+ ent->client->pers.teamSelection == PTE_NONE )
+ {
+ trap_SendServerCommand( clientNum, "print \"Join a team first\n\"" );
+ return;
+ }
+
+ if( cmds[ i ].cmdFlags & CMD_NOTEAM &&
+ ent->client->pers.teamSelection != PTE_NONE )
+ {
+ trap_SendServerCommand( clientNum,
+ "print \"Cannot use this command when on a team\n\"" );
+ return;
+ }
+
+ if( cmds[ i ].cmdFlags & CMD_ALIEN &&
+ ent->client->pers.teamSelection != PTE_ALIENS )
+ {
+ trap_SendServerCommand( clientNum,
+ "print \"Must be alien to use this command\n\"" );
+ return;
+ }
+
+ if( cmds[ i ].cmdFlags & CMD_HUMAN &&
+ ent->client->pers.teamSelection != PTE_HUMANS )
+ {
+ trap_SendServerCommand( clientNum,
+ "print \"Must be human to use this command\n\"" );
+ return;
+ }
+
+ if( cmds[ i ].cmdFlags & CMD_LIVING &&
+ ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 ||
+ ent->client->sess.sessionTeam == TEAM_SPECTATOR ) )
+ {
+ trap_SendServerCommand( clientNum,
+ "print \"Must be living to use this command\n\"" );
+ return;
+ }
+
+ cmds[ i ].cmdHandler( ent );
+}
+
+int G_SayArgc( void )
+{
+ int c = 0;
+ char *s;
+
+ s = ConcatArgs( 0 );
+ while( 1 )
+ {
+ while( *s == ' ' )
+ s++;
+ if( !*s )
+ break;
+ c++;
+ while( *s && *s != ' ' )
+ s++;
+ }
+ return c;
+}
+
+qboolean G_SayArgv( int n, char *buffer, int bufferLength )
+{
+ int bc = 0;
+ int c = 0;
+ char *s;
+
+ if( bufferLength < 1 )
+ return qfalse;
+ if( n < 0 )
+ return qfalse;
+ s = ConcatArgs( 0 );
+ while( c < n )
+ {
+ while( *s == ' ' )
+ s++;
+ if( !*s )
+ break;
+ c++;
+ while( *s && *s != ' ' )
+ s++;
+ }
+ if( c < n )
+ return qfalse;
+ while( *s == ' ' )
+ s++;
+ if( !*s )
+ return qfalse;
+ //memccpy( buffer, s, ' ', bufferLength );
+ while( bc < bufferLength - 1 && *s && *s != ' ' )
+ buffer[ bc++ ] = *s++;
+ buffer[ bc ] = 0;
+ return qtrue;
+}
+
+char *G_SayConcatArgs( int start )
+{
+ char *s;
+ int c = 0;
+
+ s = ConcatArgs( 0 );
+ while( c < start )
+ {
+ while( *s == ' ' )
+ s++;
+ if( !*s )
+ break;
+ c++;
+ while( *s && *s != ' ' )
+ s++;
+ }
+ while( *s == ' ' )
+ s++;
+ return s;
+}
+
+void G_DecolorString( char *in, char *out )
+{
+ while( *in ) {
+ if( *in == 27 || *in == '^' ) {
+ in++;
+ if( *in )
+ in++;
+ continue;
+ }
+ *out++ = *in++;
+ }
+ *out = '\0';
+}
+
+void G_ParseEscapedString( char *buffer )
+{
+ int i = 0;
+ int j = 0;
+
+ while( buffer[i] )
+ {
+ if(!buffer[i]) break;
+
+ if(buffer[i] == '\\')
+ {
+ if(buffer[i + 1] == '\\')
+ buffer[j] = buffer[++i];
+ else if(buffer[i + 1] == 'n')
+ {
+ buffer[j] = '\n';
+ i++;
+ }
+ else
+ buffer[j] = buffer[i];
+ }
+ else
+ buffer[j] = buffer[i];
+
+ i++;
+ j++;
+ }
+ buffer[j] = 0;
+}
+
+void G_WordWrap( char *buffer, int maxwidth )
+{
+ char out[ MAX_STRING_CHARS ];
+ int i = 0;
+ int j = 0;
+ int k;
+ int linecount = 0;
+ int currentcolor = 7;
+
+ while ( buffer[ j ]!='\0' )
+ {
+ if( i == ( MAX_STRING_CHARS - 1 ) )
+ break;
+
+ //If it's the start of a new line, copy over the color code,
+ //but not if we already did it, or if the text at the start of the next line is also a color code
+ if( linecount == 0 && i>2 && out[ i-2 ] != Q_COLOR_ESCAPE && out[ i-1 ] != Q_COLOR_ESCAPE )
+ {
+ out[ i ] = Q_COLOR_ESCAPE;
+ out[ i + 1 ] = '0' + currentcolor;
+ i+=2;
+ continue;
+ }
+
+ if( linecount < maxwidth )
+ {
+ out[ i ] = buffer[ j ];
+ if( out[ i ] == '\n' )
+ {
+ linecount = 0;
+ }
+ else if( Q_IsColorString( &buffer[j] ) )
+ {
+ currentcolor = buffer[j+1] - '0';
+ }
+ else
+ linecount++;
+
+ //If we're at a space and getting close to a line break, look ahead and make sure that there isn't already a \n or a closer space coming. If not, break here.
+ if( out[ i ] == ' ' && linecount >= (maxwidth - 10 ) )
+ {
+ qboolean foundbreak = qfalse;
+ for( k = i+1; k < maxwidth; k++ )
+ {
+ if( !buffer[ k ] )
+ continue;
+ if( buffer[ k ] == '\n' || buffer[ k ] == ' ' )
+ foundbreak = qtrue;
+ }
+ if( !foundbreak )
+ {
+ out [ i ] = '\n';
+ linecount = 0;
+ }
+ }
+
+ i++;
+ j++;
+ }
+ else
+ {
+ out[ i ] = '\n';
+ i++;
+ linecount = 0;
+ }
+ }
+ out[ i ] = '\0';
+
+
+ strcpy( buffer, out );
+}
+
+void G_PrivateMessage( gentity_t *ent )
+{
+ int pids[ MAX_CLIENTS ];
+ int ignoreids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ];
+ char cmd[ 12 ];
+ char str[ MAX_STRING_CHARS ];
+ char *msg;
+ char color;
+ int pcount, matches, ignored = 0;
+ int i;
+ int skipargs = 0;
+ qboolean teamonly = qfalse;
+ gentity_t *tmpent;
+
+ if( !g_privateMessages.integer && ent )
+ {
+ ADMP( "Sorry, but private messages have been disabled\n" );
+ return;
+ }
+
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
+ return;
+ }
+
+ G_SayArgv( 0, cmd, sizeof( cmd ) );
+ if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) )
+ {
+ skipargs = 1;
+ G_SayArgv( 1, cmd, sizeof( cmd ) );
+ }
+ if( G_SayArgc( ) < 3+skipargs )
+ {
+ ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) );
+ return;
+ }
+
+ if( !Q_stricmp( cmd, "mt" ) || !Q_stricmp( cmd, "/mt" ) )
+ teamonly = qtrue;
+
+ G_SayArgv( 1+skipargs, name, sizeof( name ) );
+ msg = G_SayConcatArgs( 2+skipargs );
+ pcount = G_ClientNumbersFromString( name, pids );
+
+ if( ent )
+ {
+ int count = 0;
+
+ for( i=0; i < pcount; i++ )
+ {
+ tmpent = &g_entities[ pids[ i ] ];
+
+ if( teamonly && !OnSameTeam( ent, tmpent ) )
+ continue;
+
+ // Ignore sending to invisible players
+ if( tmpent->client->sess.invisible == qtrue && !G_admin_permission( ent, "invisible" ) )
+ continue;
+
+ // Ignore sending to non-invisible-capable players while invisible
+ if( ent->client->sess.invisible == qtrue && !G_admin_permission( tmpent, "invisible" ) )
+ continue;
+
+ if( BG_ClientListTest( &tmpent->client->sess.ignoreList,
+ ent-g_entities ) )
+ {
+ ignoreids[ ignored++ ] = pids[ i ];
+ continue;
+ }
+
+ pids[ count ] = pids[ i ];
+ count++;
+ }
+ matches = count;
+ }
+ else
+ {
+ matches = pcount;
+ }
+
+ color = teamonly ? COLOR_CYAN : COLOR_YELLOW;
+
+ if( !Q_stricmp( name, "console" ) )
+ {
+ ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) );
+ ADMP( va( "^%csent to Console.\n", color ) );
+
+ G_LogPrintf( "privmsg: %s^7: Console: ^6%s^7\n",
+ ( ent ) ? ent->client->pers.netname : "Console", msg );
+
+ return;
+ }
+
+ Q_strncpyz( str,
+ va( "^%csent to %i player%s: ^7", color, matches,
+ ( matches == 1 ) ? "" : "s" ),
+ sizeof( str ) );
+
+ for( i=0; i < matches; i++ )
+ {
+ tmpent = &g_entities[ pids[ i ] ];
+
+ if( i > 0 )
+ Q_strcat( str, sizeof( str ), "^7, " );
+ Q_strcat( str, sizeof( str ), tmpent->client->pers.netname );
+ trap_SendServerCommand( pids[ i ], va(
+ "chat \"%s^%c -> ^7%s^7: (%d recipients): ^%c%s^7\" %i",
+ ( ent ) ? ent->client->pers.netname : "console",
+ color,
+ name,
+ matches,
+ color,
+ msg,
+ ent ? ent-g_entities : -1 ) );
+
+ trap_SendServerCommand( pids[ i ], va(
+ "cp \"^%cprivate message from ^7%s^7\"", color,
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+
+ if( !matches )
+ ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n",
+ name ) );
+ else
+ {
+ if( ent )
+ ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) );
+
+ ADMP( va( "%s\n", str ) );
+
+ G_LogPrintf( "%s: %s^7: %s^7: %s\n",
+ ( teamonly ) ? "tprivmsg" : "privmsg",
+ ( ent ) ? ent->client->pers.netname : "console",
+ name, msg );
+ }
+
+ if( ignored )
+ {
+ Q_strncpyz( str, va( "^%cignored by %i player%s: ^7", color, ignored,
+ ( ignored == 1 ) ? "" : "s" ), sizeof( str ) );
+ for( i=0; i < ignored; i++ )
+ {
+ tmpent = &g_entities[ ignoreids[ i ] ];
+ if( i > 0 )
+ Q_strcat( str, sizeof( str ), "^7, " );
+ Q_strcat( str, sizeof( str ), tmpent->client->pers.netname );
+ }
+ ADMP( va( "%s\n", str ) );
+ }
+}
+
+ /*
+ =================
+ Cmd_Builder_f
+ =================
+ */
+ void Cmd_Builder_f( gentity_t *ent )
+ {
+ vec3_t forward, right, up;
+ vec3_t start, end;
+ trace_t tr;
+ gentity_t *traceEnt;
+ char bdnumbchr[21];
+
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ if( ent->client->pers.teamSelection != PTE_NONE )
+ CalcMuzzlePoint( ent, forward, right, up, start );
+ else
+ VectorCopy( ent->client->ps.origin, start );
+ VectorMA( start, 1000, forward, end );
+
+ trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ Com_sprintf( bdnumbchr, sizeof(bdnumbchr), "%i", traceEnt->bdnumb );
+
+ if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) )
+ {
+ if( G_admin_permission( ent, "buildlog" ) ) {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"^5/builder:^7 ^3Building:^7 %s ^3Built By:^7 %s^7 ^3Buildlog Number:^7 %s^7\n\"",
+ BG_FindHumanNameForBuildable( traceEnt->s.modelindex ),
+ (traceEnt->bdnumb != -1) ? G_FindBuildLogName( traceEnt->bdnumb ) : "<world>",
+ (traceEnt->bdnumb != -1) ? bdnumbchr : "none" ) );
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, va(
+ "print \"^5/builder:^7 ^3Building:^7 %s ^3Built By:^7 %s^7\n\"",
+ BG_FindHumanNameForBuildable( traceEnt->s.modelindex ),
+ (traceEnt->bdnumb != -1) ? G_FindBuildLogName( traceEnt->bdnumb ) : "<world>" ) );
+ }
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"^5/builder:^7 No structure found in your crosshair. Please face a structure and try again.\n\"" );
+ }
+ }
+
+void G_CP( gentity_t *ent )
+{
+ int i;
+ char buffer[MAX_STRING_CHARS];
+ char prefixes[MAX_STRING_CHARS] = "";
+ char wrappedtext[ MAX_STRING_CHARS ] = "";
+ char *ptr;
+ char *text;
+ qboolean sendAliens = qtrue;
+ qboolean sendHumans = qtrue;
+ qboolean sendSpecs = qtrue;
+ Q_strncpyz( buffer, ConcatArgs( 1 ), sizeof( buffer ) );
+ G_ParseEscapedString( buffer );
+
+ if( strstr( buffer, "!cp" ) )
+ {
+ ptr = buffer;
+ while( *ptr != '!' )
+ ptr++;
+ ptr+=4;
+
+ Q_strncpyz( buffer, ptr, sizeof(buffer) );
+ }
+
+ text = buffer;
+
+ ptr = buffer;
+ while( *ptr == ' ' )
+ ptr++;
+ if( *ptr == '-' )
+ {
+ sendAliens = qfalse;
+ sendHumans = qfalse;
+ sendSpecs = qfalse;
+ Q_strcat( prefixes, sizeof( prefixes ), " " );
+ ptr++;
+
+ while( *ptr && *ptr != ' ' )
+ {
+ if( !sendAliens && ( *ptr == 'a' || *ptr == 'A' ) )
+ {
+ sendAliens = qtrue;
+ Q_strcat( prefixes, sizeof( prefixes ), "[^1A^7]" );
+ }
+ if( !sendHumans && ( *ptr == 'h' || *ptr == 'H' ) )
+ {
+ sendHumans = qtrue;
+ Q_strcat( prefixes, sizeof( prefixes ), "[^4H^7]" );
+ }
+ if( !sendSpecs && ( *ptr == 's' || *ptr == 'S' ) )
+ {
+ sendSpecs = qtrue;
+ Q_strcat( prefixes, sizeof( prefixes ), "[^3S^7]" );
+ }
+ ptr++;
+ }
+ if( *ptr ) text = ptr+1;
+ else text = ptr;
+ }
+
+ strcpy( wrappedtext, text );
+
+ if( strlen( text ) == 0 ) return;
+
+ G_WordWrap( wrappedtext, 50 );
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( ( !sendAliens && level.clients[ i ].pers.teamSelection == PTE_ALIENS ) ||
+ ( !sendHumans && level.clients[ i ].pers.teamSelection == PTE_HUMANS ) ||
+ ( !sendSpecs && level.clients[ i ].pers.teamSelection == PTE_NONE ) )
+ {
+ if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) )
+ {
+ trap_SendServerCommand( i, va("print \"^6[Admins]^7 CP to other team%s: %s \n\"", prefixes, text ) );
+ }
+ continue;
+ }
+
+ trap_SendServerCommand( i, va( "cp \"%s\"", wrappedtext ) );
+ trap_SendServerCommand( i, va( "print \"%s^7 CP%s: %s\n\"", ( ent ? G_admin_adminPrintName( ent ) : "console" ), prefixes, text ) );
+ }
+
+ G_Printf( "cp: %s\n", ConcatArgs( 1 ) );
+}
+
+/*
+=================
+G_IsMuted
+
+Check if a player is muted
+=================
+*/
+qboolean G_IsMuted( gclient_t *client )
+{
+ qboolean muteState = qfalse;
+
+ //check if mute has expired
+ if( client->pers.muteExpires ) {
+ if( client->pers.muteExpires < level.time )
+ {
+ client->pers.muted = qfalse;
+ client->pers.muteExpires = 0;
+ }
+ }
+
+ if( client->pers.muted )
+ muteState = qtrue;
+
+ return muteState;
+}
+
+/*
+==================
+G_TeamKill_Repent
+
+Determine whether a players team kill activity is high
+==================
+*/
+
+qboolean G_TeamKill_Repent( gentity_t *ent )
+{
+ int millisSinceLastTeamKill;
+
+ // Doesn't work if g_teamKillThreshold isn't set
+ if( !g_teamKillThreshold.integer ||
+ g_teamKillThreshold.integer == 0 )
+ return qfalse;
+
+ // Doesn't work when game is paused
+ if( level.paused )
+ return qfalse;
+
+ millisSinceLastTeamKill = level.time - ent->client->pers.lastTeamKillTime;
+ if( millisSinceLastTeamKill < 30000 )
+ ent->client->pers.teamKillDemerits++;
+ else
+ {
+ ent->client->pers.teamKillDemerits--;
+ if( ent->client->pers.teamKillDemerits < 0 )
+ ent->client->pers.teamKillDemerits = 0;
+ }
+
+ ent->client->pers.lastTeamKillTime = level.time;
+
+ if ( ent->client->pers.teamKillDemerits >= ( g_teamKillThreshold.integer + 2 ) )
+ trap_SendConsoleCommand( 0, va( "!ban %s 30m team killing\n", ent->client->pers.ip ) );
+ else if ( ent->client->pers.teamKillDemerits == ( g_teamKillThreshold.integer + 1 ) )
+ trap_SendConsoleCommand( 0, va( "!warn %i team killing\n", ent->client->ps.clientNum ) );
+ else if ( ent->client->pers.teamKillDemerits == g_teamKillThreshold.integer )
+ G_AdminsPrintf( "Team killer %s^7 has team killed ^6%i^7 times.\n",
+ ent->client->pers.netname,
+ ent->client->pers.statscounters.teamkills );
+
+ return qfalse;
+}
diff --git a/src/game/g_combat.c b/src/game/g_combat.c
new file mode 100644
index 0000000..569a613
--- /dev/null
+++ b/src/game/g_combat.c
@@ -0,0 +1,1758 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_LOCDAMAGE_REGIONS ];
+int g_numDamageRegions[ PCL_NUM_CLASSES ];
+
+armourRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_ARMOUR_REGIONS ];
+int g_numArmourRegions[ UP_NUM_UPGRADES ];
+
+/*
+============
+AddScore
+
+Adds score to both the client and his team
+============
+*/
+void AddScore( gentity_t *ent, int score )
+{
+ if( !ent->client )
+ return;
+
+ ent->client->ps.persistant[ PERS_SCORE ] += score;
+ CalculateRanks( );
+}
+
+/*
+==================
+LookAtKiller
+==================
+*/
+void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker )
+{
+ vec3_t dir;
+
+ if ( attacker && attacker != self )
+ VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dir );
+ else if( inflictor && inflictor != self )
+ VectorSubtract( inflictor->s.pos.trBase, self->s.pos.trBase, dir );
+ else
+ {
+ self->client->ps.stats[ STAT_VIEWLOCK ] = self->s.angles[ YAW ];
+ return;
+ }
+
+ self->client->ps.stats[ STAT_VIEWLOCK ] = vectoyaw( dir );
+}
+
+// these are just for logging, the client prints its own messages
+char *modNames[ ] =
+{
+ "MOD_UNKNOWN",
+ "MOD_SHOTGUN",
+ "MOD_BLASTER",
+ "MOD_PAINSAW",
+ "MOD_MACHINEGUN",
+ "MOD_CHAINGUN",
+ "MOD_PRIFLE",
+ "MOD_MDRIVER",
+ "MOD_LASGUN",
+ "MOD_LCANNON",
+ "MOD_LCANNON_SPLASH",
+ "MOD_FLAMER",
+ "MOD_FLAMER_SPLASH",
+ "MOD_GRENADE",
+ "MOD_WATER",
+ "MOD_SLIME",
+ "MOD_LAVA",
+ "MOD_CRUSH",
+ "MOD_TELEFRAG",
+ "MOD_FALLING",
+ "MOD_SUICIDE",
+ "MOD_TARGET_LASER",
+ "MOD_TRIGGER_HURT",
+
+ "MOD_ABUILDER_CLAW",
+ "MOD_LEVEL0_BITE",
+ "MOD_LEVEL1_CLAW",
+ "MOD_LEVEL1_PCLOUD",
+ "MOD_LEVEL3_CLAW",
+ "MOD_LEVEL3_POUNCE",
+ "MOD_LEVEL3_BOUNCEBALL",
+ "MOD_LEVEL2_CLAW",
+ "MOD_LEVEL2_ZAP",
+ "MOD_LEVEL4_CLAW",
+ "MOD_LEVEL4_CHARGE",
+
+ "MOD_SLOWBLOB",
+ "MOD_POISON",
+ "MOD_SWARM",
+
+ "MOD_HSPAWN",
+ "MOD_TESLAGEN",
+ "MOD_MGTURRET",
+ "MOD_REACTOR",
+
+ "MOD_ASPAWN",
+ "MOD_ATUBE",
+ "MOD_OVERMIND",
+ "MOD_SLAP"
+};
+
+/*
+==================
+player_die
+==================
+*/
+void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
+{
+ gentity_t *ent;
+ int anim;
+ int killer;
+ int i, j;
+ char *killerName, *obit;
+ float totalTK = 0;
+ float totalDamage = 0.0f;
+ float percentDamage = 0.0f;
+ gentity_t *player;
+ qboolean tk = qfalse;
+
+
+ if( self->client->ps.pm_type == PM_DEAD )
+ return;
+
+
+ if( level.intermissiontime )
+ return;
+
+ self->client->ps.pm_type = PM_DEAD;
+ self->suicideTime = 0;
+
+ if( attacker )
+ {
+ killer = attacker->s.number;
+
+ if( attacker->client )
+ {
+ killerName = attacker->client->pers.netname;
+ tk = ( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ]
+ == self->client->ps.stats[ STAT_PTEAM ] );
+
+ if( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ] == self->client->ps.stats[ STAT_PTEAM ] )
+ {
+ attacker->client->pers.statscounters.teamkills++;
+ if( attacker->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.teamkills++;
+ }
+ else if( attacker->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.teamkills++;
+ }
+ }
+
+ }
+ else
+ killerName = "<non-client>";
+ }
+ else
+ {
+ killer = ENTITYNUM_WORLD;
+ killerName = "<world>";
+ }
+
+ if( killer < 0 || killer >= MAX_CLIENTS )
+ {
+ killer = ENTITYNUM_WORLD;
+ killerName = "<world>";
+ }
+
+ if( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) )
+ obit = "<bad obituary>";
+ else
+ obit = modNames[ meansOfDeath ];
+
+ G_LogPrintf("Kill: %i %i %i: %s^7 killed %s^7 by %s\n",
+ killer, self->s.number, meansOfDeath, killerName,
+ self->client->pers.netname, obit );
+
+ //TA: deactivate all upgrades
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ BG_DeactivateUpgrade( i, self->client->ps.stats );
+
+ if( meansOfDeath == MOD_SLAP )
+ {
+ trap_SendServerCommand( -1,
+ va( "print \"%s^7 felt %s^7's authority\n\"",
+ self->client->pers.netname, killerName ) );
+ goto finish_dying;
+ }
+
+ // broadcast the death event to everyone
+ if( !tk )
+ {
+ ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
+ ent->s.eventParm = meansOfDeath;
+ ent->s.otherEntityNum = self->s.number;
+ ent->s.otherEntityNum2 = killer;
+ ent->r.svFlags = SVF_BROADCAST; // send to everyone
+ }
+ else if( attacker && attacker->client )
+ {
+ // tjw: obviously this is a hack and belongs in the client, but
+ // this works as a temporary fix.
+ trap_SendServerCommand( -1,
+ va( "print \"%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n\"",
+ self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] ) );
+ trap_SendServerCommand( attacker - g_entities,
+ va( "cp \"You killed ^1TEAMMATE^7 %s\"", self->client->pers.netname ) );
+ G_LogOnlyPrintf("%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n",
+ self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] );
+ G_TeamKill_Repent( attacker );
+ }
+
+ self->enemy = attacker;
+
+ self->client->ps.persistant[ PERS_KILLED ]++;
+ self->client->pers.statscounters.deaths++;
+ if( self->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.deaths++;
+ }
+ else if( self->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.deaths++;
+ }
+
+ if( attacker && attacker->client )
+ {
+ attacker->client->lastkilled_client = self->s.number;
+
+ if( g_killerHP.integer ||
+ ( g_devmapKillerHP.integer && g_cheats.integer ) )
+ {
+ trap_SendServerCommand( self-g_entities,
+ va( "print \"Your killer, %s^7, had %3i HP.\n\"",
+ killerName, attacker->health ) );
+ }
+
+ if( attacker == self || OnSameTeam( self, attacker ) )
+ {
+ AddScore( attacker, -1 );
+
+ // Normal teamkill penalty
+ if( !g_retribution.integer )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ G_AddCreditToClient( attacker->client, -FREEKILL_ALIEN, qtrue );
+ else if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_AddCreditToClient( attacker->client, -FREEKILL_HUMAN, qtrue );
+ }
+ }
+ else
+ {
+ AddScore( attacker, 1 );
+
+ attacker->client->lastKillTime = level.time;
+ attacker->client->pers.statscounters.kills++;
+ if( attacker->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.kills++;
+ }
+ else if( attacker->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.kills++;
+ }
+ }
+
+ if( attacker == self )
+ {
+ attacker->client->pers.statscounters.suicides++;
+ if( attacker->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.suicides++;
+ }
+ else if( attacker->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.suicides++;
+ }
+ }
+ }
+ else if( attacker->s.eType != ET_BUILDABLE )
+ AddScore( self, -1 );
+
+ //total up all the damage done by every client
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ totalDamage += (float)self->credits[ i ];
+ totalTK += (float)self->client->tkcredits[ i ];
+ }
+ // punish players for damaging teammates
+ if ( g_retribution.integer && totalTK )
+ {
+ int totalPrice;
+ int max = HUMAN_MAX_CREDITS;
+
+ if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ totalPrice = BG_ClassCanEvolveFromTo( PCL_ALIEN_LEVEL0, self->client->ps.stats[ STAT_PCLASS ], ALIEN_MAX_KILLS, 0 );
+ max = ALIEN_MAX_KILLS;
+ }
+ else
+ {
+ totalPrice = BG_GetValueOfEquipment( &self->client->ps );
+ }
+
+ if ( self->client->ps.persistant[ PERS_CREDIT ] + totalPrice > max )
+ totalPrice = max - self->client->ps.persistant[ PERS_CREDIT ];
+
+ if ( totalPrice > 0 )
+ {
+ totalTK += totalDamage;
+ if( totalTK < self->client->ps.stats[ STAT_MAX_HEALTH ] )
+ totalTK = self->client->ps.stats[ STAT_MAX_HEALTH ];
+
+ if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ for ( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ int price;
+ // no retribution if self damage or enemy damage or building damage or no damage from this client
+ if ( i == self - g_entities || !g_entities[ i ].client ||
+ !OnSameTeam( &g_entities[ i ], self ) ||
+ !self->client->tkcredits[ i ] )
+ continue;
+
+ // calculate retribution price (rounded up)
+ price = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK + 0.5f;
+ self->client->tkcredits[ i ] = 0;
+
+ // check for enough credits
+ if ( g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] < price )
+ price = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ];
+ if ( price )
+ {
+ G_AddCreditToClient( self->client, price, qtrue );
+ G_AddCreditToClient( g_entities[ i ].client, -price, qtrue );
+
+ trap_SendServerCommand( self->client->ps.clientNum,
+ va( "print \"Received ^3%d credits ^7from %s ^7in retribution.\n\"",
+ price, g_entities[ i ].client->pers.netname ) );
+ trap_SendServerCommand( g_entities[ i ].client->ps.clientNum,
+ va( "print \"Transfered ^3%d credits ^7to %s ^7in retribution.\n\"",
+ price, self->client->pers.netname ) );
+ }
+ }
+ }
+ else
+ {
+ int toPay[ MAX_CLIENTS ] = { 0 };
+ int frags = totalPrice;
+ int damageForEvo = totalTK / totalPrice;
+ for ( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ // no retribution if self damage or enemy damage or building damage or no damage from this client
+ if ( i == self - g_entities || !g_entities[ i ].client ||
+ !OnSameTeam( &g_entities[ i ], self ) ||
+ !self->client->tkcredits[ i ] )
+ continue;
+
+ // find out how many full evos this client needs to pay
+ toPay[ i ] = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK;
+ if ( toPay[ i ] > g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] )
+ toPay[ i ] = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ];
+ frags -= toPay[ i ];
+ self->client->tkcredits[ i ] -= damageForEvo * toPay[ i ];
+ }
+
+ // if we have not met the evo count, continue stealing evos
+ while ( 1 )
+ {
+ int maximum = 0;
+ int topClient = 0;
+ for ( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if ( self->client->tkcredits[ i ] > maximum && g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] )
+ {
+ maximum = self->client->tkcredits[ i ];
+ topClient = i;
+ }
+ }
+ if ( !maximum )
+ break;
+ toPay[ topClient ]++;
+ self->client->tkcredits[ topClient ] = 0;
+ frags--;
+ if ( !frags )
+ break;
+ }
+
+ // now move the evos around
+ for ( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if ( !toPay[ i ] )
+ continue;
+
+ G_AddCreditToClient( self->client, toPay[ i ], qtrue );
+ G_AddCreditToClient( g_entities[ i ].client, -toPay[ i ], qtrue );
+
+ trap_SendServerCommand( self->client->ps.clientNum,
+ va( "print \"Received ^3%d ^7evos from %s ^7in retribution.\n\"",
+ toPay[ i ], g_entities[ i ].client->pers.netname ) );
+ trap_SendServerCommand( g_entities[ i ].client->ps.clientNum,
+ va( "print \"Transfered ^3%d ^7evos to %s ^7in retribution.\n\"",
+ toPay[ i ], self->client->pers.netname ) );
+ }
+ }
+ }
+ }
+
+ // if players did more than DAMAGE_FRACTION_FOR_KILL increment the stage counters
+ if( !OnSameTeam( self, attacker ) && totalDamage >= ( self->client->ps.stats[ STAT_MAX_HEALTH ] * DAMAGE_FRACTION_FOR_KILL ) )
+ {
+ if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ trap_Cvar_Set( "g_alienKills", va( "%d", g_alienKills.integer + 1 ) );
+ if( g_alienStage.integer < 2 )
+ {
+ self->client->pers.statscounters.feeds++;
+ level.humanStatsCounters.feeds++;
+ }
+ }
+ else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ trap_Cvar_Set( "g_humanKills", va( "%d", g_humanKills.integer + 1 ) );
+ if( g_humanStage.integer < 2 )
+ {
+ self->client->pers.statscounters.feeds++;
+ level.alienStatsCounters.feeds++;
+ }
+ }
+ }
+
+ if( totalDamage > 0.0f )
+ {
+ if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //nice simple happy bouncy human land
+ float classValue = BG_FindValueOfClass( self->client->ps.stats[ STAT_PCLASS ] );
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ player = g_entities + i;
+
+ if( !player->client )
+ continue;
+
+ if( player->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ continue;
+
+ if( !self->credits[ i ] )
+ continue;
+
+ percentDamage = (float)self->credits[ i ] / totalDamage;
+ if( percentDamage > 0 && percentDamage < 1)
+ {
+ player->client->pers.statscounters.assists++;
+ level.humanStatsCounters.assists++;
+ }
+
+ //add credit
+ G_AddCreditToClient( player->client,
+ (int)( classValue * percentDamage ), qtrue );
+ }
+ }
+ else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //horribly complex nasty alien land
+ float humanValue = BG_GetValueOfHuman( &self->client->ps );
+ int frags;
+ int unclaimedFrags = (int)humanValue;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ player = g_entities + i;
+
+ if( !player->client )
+ continue;
+
+ if( player->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ continue;
+
+ //this client did no damage
+ if( !self->credits[ i ] )
+ continue;
+
+ //nothing left to claim
+ if( !unclaimedFrags )
+ break;
+
+ percentDamage = (float)self->credits[ i ] / totalDamage;
+ if( percentDamage > 0 && percentDamage < 1)
+ {
+ player->client->pers.statscounters.assists++;
+ level.alienStatsCounters.assists++;
+ }
+
+ frags = (int)floor( humanValue * percentDamage);
+
+ if( frags > 0 )
+ {
+ //add kills
+ G_AddCreditToClient( player->client, frags, qtrue );
+
+ //can't revist this account later
+ self->credits[ i ] = 0;
+
+ //reduce frags left to be claimed
+ unclaimedFrags -= frags;
+ }
+ }
+
+ //there are frags still to be claimed
+ if( unclaimedFrags )
+ {
+ //the clients remaining at this point do not
+ //have enough credit to claim even one frag
+ //so simply give the top <unclaimedFrags> clients
+ //a frag each
+
+ for( i = 0; i < unclaimedFrags; i++ )
+ {
+ int maximum = 0;
+ int topClient = 0;
+
+ for( j = 0; j < MAX_CLIENTS; j++ )
+ {
+ //this client did no damage
+ if( !self->credits[ j ] )
+ continue;
+
+ if( self->credits[ j ] > maximum )
+ {
+ maximum = self->credits[ j ];
+ topClient = j;
+ }
+ }
+
+ if( maximum > 0 )
+ {
+ player = g_entities + topClient;
+
+ //add kills
+ G_AddCreditToClient( player->client, 1, qtrue );
+
+ //can't revist this account again
+ self->credits[ topClient ] = 0;
+ }
+ }
+ }
+ }
+ }
+
+ ScoreboardMessage( self ); // show scores
+
+ // send updated scores to any clients that are following this one,
+ // or they would get stale scoreboards
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ gclient_t *client;
+
+ client = &level.clients[ i ];
+ if( client->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( client->sess.sessionTeam != TEAM_SPECTATOR )
+ continue;
+
+ if( client->sess.spectatorClient == self->s.number )
+ ScoreboardMessage( g_entities + i );
+ }
+
+finish_dying: // from MOD_SLAP
+
+ VectorCopy( self->s.origin, self->client->pers.lastDeathLocation );
+
+ self->takedamage = qfalse; // can still be gibbed
+
+ self->s.weapon = WP_NONE;
+ self->r.contents = CONTENTS_CORPSE;
+
+ self->s.angles[ PITCH ] = 0;
+ self->s.angles[ ROLL ] = 0;
+ self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ];
+ LookAtKiller( self, inflictor, attacker );
+
+ VectorCopy( self->s.angles, self->client->ps.viewangles );
+
+ self->s.loopSound = 0;
+
+ self->r.maxs[ 2 ] = -8;
+
+ // don't allow respawn until the death anim is done
+ // g_forcerespawn may force spawning at some later time
+ self->client->respawnTime = level.time + 1700;
+
+ // clear misc
+ memset( self->client->ps.misc, 0, sizeof( self->client->ps.misc ) );
+
+ {
+ // normal death
+ static int i;
+
+ if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ {
+ switch( i )
+ {
+ case 0:
+ anim = BOTH_DEATH1;
+ break;
+ case 1:
+ anim = BOTH_DEATH2;
+ break;
+ case 2:
+ default:
+ anim = BOTH_DEATH3;
+ break;
+ }
+ }
+ else
+ {
+ switch( i )
+ {
+ case 0:
+ anim = NSPA_DEATH1;
+ break;
+ case 1:
+ anim = NSPA_DEATH2;
+ break;
+ case 2:
+ default:
+ anim = NSPA_DEATH3;
+ break;
+ }
+ }
+
+ self->client->ps.legsAnim =
+ ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
+
+ if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
+ {
+ self->client->ps.torsoAnim =
+ ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
+ }
+
+ // use own entityid if killed by non-client to prevent uint8_t overflow
+ G_AddEvent( self, EV_DEATH1 + i,
+ ( killer < MAX_CLIENTS ) ? killer : self - g_entities );
+
+ // globally cycle through the different death animations
+ i = ( i + 1 ) % 3;
+ }
+
+ trap_LinkEntity( self );
+}
+
+
+////////TA: locdamage
+
+/*
+===============
+G_ParseArmourScript
+===============
+*/
+void G_ParseArmourScript( char *buf, int upgrade )
+{
+ char *token;
+ int count;
+
+ count = 0;
+
+ while( 1 )
+ {
+ token = COM_Parse( &buf );
+
+ if( !token[0] )
+ break;
+
+ if( strcmp( token, "{" ) )
+ {
+ G_Printf( "Missing { in armour file\n" );
+ break;
+ }
+
+ if( count == MAX_ARMOUR_REGIONS )
+ {
+ G_Printf( "Max armour regions exceeded in locdamage file\n" );
+ break;
+ }
+
+ //default
+ g_armourRegions[ upgrade ][ count ].minHeight = 0.0;
+ g_armourRegions[ upgrade ][ count ].maxHeight = 1.0;
+ g_armourRegions[ upgrade ][ count ].minAngle = 0;
+ g_armourRegions[ upgrade ][ count ].maxAngle = 360;
+ g_armourRegions[ upgrade ][ count ].modifier = 1.0;
+ g_armourRegions[ upgrade ][ count ].crouch = qfalse;
+
+ while( 1 )
+ {
+ token = COM_ParseExt( &buf, qtrue );
+
+ if( !token[0] )
+ {
+ G_Printf( "Unexpected end of armour file\n" );
+ break;
+ }
+
+ if( !Q_stricmp( token, "}" ) )
+ {
+ break;
+ }
+ else if( !strcmp( token, "minHeight" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "0" );
+
+ g_armourRegions[ upgrade ][ count ].minHeight = atof( token );
+ }
+ else if( !strcmp( token, "maxHeight" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "100" );
+
+ g_armourRegions[ upgrade ][ count ].maxHeight = atof( token );
+ }
+ else if( !strcmp( token, "minAngle" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "0" );
+
+ g_armourRegions[ upgrade ][ count ].minAngle = atoi( token );
+ }
+ else if( !strcmp( token, "maxAngle" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "360" );
+
+ g_armourRegions[ upgrade ][ count ].maxAngle = atoi( token );
+ }
+ else if( !strcmp( token, "modifier" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "1.0" );
+
+ g_armourRegions[ upgrade ][ count ].modifier = atof( token );
+ }
+ else if( !strcmp( token, "crouch" ) )
+ {
+ g_armourRegions[ upgrade ][ count ].crouch = qtrue;
+ }
+ }
+
+ g_numArmourRegions[ upgrade ]++;
+ count++;
+ }
+}
+
+
+/*
+===============
+G_ParseDmgScript
+===============
+*/
+void G_ParseDmgScript( char *buf, int class )
+{
+ char *token;
+ int count;
+
+ count = 0;
+
+ while( 1 )
+ {
+ token = COM_Parse( &buf );
+
+ if( !token[0] )
+ break;
+
+ if( strcmp( token, "{" ) )
+ {
+ G_Printf( "Missing { in locdamage file\n" );
+ break;
+ }
+
+ if( count == MAX_LOCDAMAGE_REGIONS )
+ {
+ G_Printf( "Max damage regions exceeded in locdamage file\n" );
+ break;
+ }
+
+ //default
+ g_damageRegions[ class ][ count ].minHeight = 0.0;
+ g_damageRegions[ class ][ count ].maxHeight = 1.0;
+ g_damageRegions[ class ][ count ].minAngle = 0;
+ g_damageRegions[ class ][ count ].maxAngle = 360;
+ g_damageRegions[ class ][ count ].modifier = 1.0;
+ g_damageRegions[ class ][ count ].crouch = qfalse;
+
+ while( 1 )
+ {
+ token = COM_ParseExt( &buf, qtrue );
+
+ if( !token[0] )
+ {
+ G_Printf( "Unexpected end of locdamage file\n" );
+ break;
+ }
+
+ if( !Q_stricmp( token, "}" ) )
+ {
+ break;
+ }
+ else if( !strcmp( token, "minHeight" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "0" );
+
+ g_damageRegions[ class ][ count ].minHeight = atof( token );
+ }
+ else if( !strcmp( token, "maxHeight" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "100" );
+
+ g_damageRegions[ class ][ count ].maxHeight = atof( token );
+ }
+ else if( !strcmp( token, "minAngle" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "0" );
+
+ g_damageRegions[ class ][ count ].minAngle = atoi( token );
+ }
+ else if( !strcmp( token, "maxAngle" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "360" );
+
+ g_damageRegions[ class ][ count ].maxAngle = atoi( token );
+ }
+ else if( !strcmp( token, "modifier" ) )
+ {
+ token = COM_ParseExt( &buf, qfalse );
+
+ if ( !token[0] )
+ strcpy( token, "1.0" );
+
+ g_damageRegions[ class ][ count ].modifier = atof( token );
+ }
+ else if( !strcmp( token, "crouch" ) )
+ {
+ g_damageRegions[ class ][ count ].crouch = qtrue;
+ }
+ }
+
+ g_numDamageRegions[ class ]++;
+ count++;
+ }
+}
+
+
+/*
+============
+G_CalcDamageModifier
+============
+*/
+static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags )
+{
+ vec3_t targOrigin;
+ vec3_t bulletPath;
+ vec3_t bulletAngle;
+ vec3_t pMINUSfloor, floor, normal;
+
+ float clientHeight, hitRelative, hitRatio;
+ int bulletRotation, clientRotation, hitRotation;
+ float modifier = 1.0f;
+ int i, j;
+
+ if( point == NULL )
+ return 1.0f;
+
+ if( g_unlagged.integer && targ->client && targ->client->unlaggedCalc.used )
+ VectorCopy( targ->client->unlaggedCalc.origin, targOrigin );
+ else
+ VectorCopy( targ->r.currentOrigin, targOrigin );
+
+ clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ];
+
+ if( targ->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ VectorCopy( targ->client->ps.grapplePoint, normal );
+ else
+ VectorSet( normal, 0, 0, 1 );
+
+ VectorMA( targOrigin, targ->r.mins[ 2 ], normal, floor );
+ VectorSubtract( point, floor, pMINUSfloor );
+
+ hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal );
+
+ if( hitRelative < 0.0f )
+ hitRelative = 0.0f;
+
+ if( hitRelative > clientHeight )
+ hitRelative = clientHeight;
+
+ hitRatio = hitRelative / clientHeight;
+
+ VectorSubtract( targOrigin, point, bulletPath );
+ vectoangles( bulletPath, bulletAngle );
+
+ clientRotation = targ->client->ps.viewangles[ YAW ];
+ bulletRotation = bulletAngle[ YAW ];
+
+ hitRotation = abs( clientRotation - bulletRotation );
+
+ hitRotation = hitRotation % 360; // Keep it in the 0-359 range
+
+ if( dflags & DAMAGE_NO_LOCDAMAGE )
+ {
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ float totalModifier = 0.0f;
+ float averageModifier = 1.0f;
+
+ //average all of this upgrade's armour regions together
+ if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) )
+ {
+ for( j = 0; j < g_numArmourRegions[ i ]; j++ )
+ totalModifier += g_armourRegions[ i ][ j ].modifier;
+
+ if( g_numArmourRegions[ i ] )
+ averageModifier = totalModifier / g_numArmourRegions[ i ];
+ else
+ averageModifier = 1.0f;
+ }
+
+ modifier *= averageModifier;
+ }
+ }
+ else
+ {
+ if( attacker && attacker->client )
+ {
+ attacker->client->pers.statscounters.hitslocational++;
+ level.alienStatsCounters.hitslocational++;
+ }
+ for( i = 0; i < g_numDamageRegions[ class ]; i++ )
+ {
+ qboolean rotationBound;
+
+ if( g_damageRegions[ class ][ i ].minAngle >
+ g_damageRegions[ class ][ i ].maxAngle )
+ {
+ rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle &&
+ hitRotation <= 360 ) || ( hitRotation >= 0 &&
+ hitRotation <= g_damageRegions[ class ][ i ].maxAngle );
+ }
+ else
+ {
+ rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle &&
+ hitRotation <= g_damageRegions[ class ][ i ].maxAngle );
+ }
+
+ if( rotationBound &&
+ hitRatio >= g_damageRegions[ class ][ i ].minHeight &&
+ hitRatio <= g_damageRegions[ class ][ i ].maxHeight &&
+ ( g_damageRegions[ class ][ i ].crouch ==
+ ( targ->client->ps.pm_flags & PMF_DUCKED ) ) )
+ modifier *= g_damageRegions[ class ][ i ].modifier;
+ }
+
+ if( attacker && attacker->client && modifier == 2 )
+ {
+ attacker->client->pers.statscounters.headshots++;
+ level.alienStatsCounters.headshots++;
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) )
+ {
+ for( j = 0; j < g_numArmourRegions[ i ]; j++ )
+ {
+ qboolean rotationBound;
+
+ if( g_armourRegions[ i ][ j ].minAngle >
+ g_armourRegions[ i ][ j ].maxAngle )
+ {
+ rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle &&
+ hitRotation <= 360 ) || ( hitRotation >= 0 &&
+ hitRotation <= g_armourRegions[ i ][ j ].maxAngle );
+ }
+ else
+ {
+ rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle &&
+ hitRotation <= g_armourRegions[ i ][ j ].maxAngle );
+ }
+
+ if( rotationBound &&
+ hitRatio >= g_armourRegions[ i ][ j ].minHeight &&
+ hitRatio <= g_armourRegions[ i ][ j ].maxHeight &&
+ ( g_armourRegions[ i ][ j ].crouch ==
+ ( targ->client->ps.pm_flags & PMF_DUCKED ) ) )
+ modifier *= g_armourRegions[ i ][ j ].modifier;
+ }
+ }
+ }
+ }
+
+ return modifier;
+}
+
+
+/*
+============
+G_InitDamageLocations
+============
+*/
+void G_InitDamageLocations( void )
+{
+ char *modelName;
+ char filename[ MAX_QPATH ];
+ int i;
+ int len;
+ fileHandle_t fileHandle;
+ char buffer[ MAX_LOCDAMAGE_TEXT ];
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ modelName = BG_FindModelNameForClass( i );
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/locdamage.cfg", modelName );
+
+ len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ );
+ if ( !fileHandle )
+ {
+ G_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
+ continue;
+ }
+
+ if( len >= MAX_LOCDAMAGE_TEXT )
+ {
+ G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) );
+ trap_FS_FCloseFile( fileHandle );
+ continue;
+ }
+
+ trap_FS_Read( buffer, len, fileHandle );
+ buffer[len] = 0;
+ trap_FS_FCloseFile( fileHandle );
+
+ G_ParseDmgScript( buffer, i );
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ modelName = BG_FindNameForUpgrade( i );
+ Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName );
+
+ len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ );
+
+ //no file - no parsage
+ if ( !fileHandle )
+ continue;
+
+ if( len >= MAX_LOCDAMAGE_TEXT )
+ {
+ G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) );
+ trap_FS_FCloseFile( fileHandle );
+ continue;
+ }
+
+ trap_FS_Read( buffer, len, fileHandle );
+ buffer[len] = 0;
+ trap_FS_FCloseFile( fileHandle );
+
+ G_ParseArmourScript( buffer, i );
+ }
+}
+
+////////TA: locdamage
+
+
+/*
+============
+T_Damage
+
+targ entity that is being damaged
+inflictor entity that is causing the damage
+attacker entity that caused the inflictor to damage targ
+ example: targ=monster, inflictor=rocket, attacker=player
+
+dir direction of the attack for knockback
+point point at which the damage is being inflicted, used for headshots
+damage amount of damage being inflicted
+knockback force to be applied against targ as a result of the damage
+
+inflictor, attacker, dir, and point can be NULL for environmental effects
+
+dflags these flags are used to control how T_Damage works
+ DAMAGE_RADIUS damage was indirect (from a nearby explosion)
+ DAMAGE_NO_ARMOR armor does not protect from this damage
+ DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
+ DAMAGE_NO_PROTECTION kills godmode, armor, everything
+============
+*/
+
+//TA: team is the team that is immune to this damage
+void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
+ vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team )
+{
+ if( targ->client && ( team != targ->client->ps.stats[ STAT_PTEAM ] ) )
+ G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod );
+}
+
+void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
+ vec3_t dir, vec3_t point, int damage, int dflags, int mod )
+{
+ gclient_t *client;
+ int take;
+ int save;
+ int asave = 0;
+ int knockback;
+ float damagemodifier=0.0;
+ int takeNoOverkill;
+
+ if( !targ->takedamage )
+ return;
+
+ // the intermission has allready been qualified for, so don't
+ // allow any extra scoring
+ if( level.intermissionQueued )
+ return;
+
+ if( !inflictor )
+ inflictor = &g_entities[ ENTITYNUM_WORLD ];
+
+ if( !attacker )
+ attacker = &g_entities[ ENTITYNUM_WORLD ];
+
+ if( attacker->client && attacker->client->pers.paused )
+ return;
+
+ // shootable doors / buttons don't actually have any health
+ if( targ->s.eType == ET_MOVER )
+ {
+ if( targ->use && ( targ->moverState == MOVER_POS1 ||
+ targ->moverState == ROTATOR_POS1 ) )
+ targ->use( targ, inflictor, attacker );
+
+ return;
+ }
+
+ client = targ->client;
+
+ if( client )
+ {
+ if( client->noclip && !g_devmapNoGod.integer)
+ return;
+ if( client->pers.paused )
+ return;
+ }
+
+ if( !dir )
+ dflags |= DAMAGE_NO_KNOCKBACK;
+ else
+ VectorNormalize( dir );
+
+ knockback = damage;
+
+ if( inflictor->s.weapon != WP_NONE )
+ {
+ knockback = (int)( (float)knockback *
+ BG_FindKnockbackScaleForWeapon( inflictor->s.weapon ) );
+ }
+
+ if( targ->client )
+ {
+ knockback = (int)( (float)knockback *
+ BG_FindKnockbackScaleForClass( targ->client->ps.stats[ STAT_PCLASS ] ) );
+ }
+
+ if( knockback > 200 )
+ knockback = 200;
+
+ if( targ->flags & FL_NO_KNOCKBACK )
+ knockback = 0;
+
+ if( dflags & DAMAGE_NO_KNOCKBACK )
+ knockback = 0;
+
+ // figure momentum add, even if the damage won't be taken
+ if( knockback && targ->client )
+ {
+ vec3_t kvel;
+ float mass;
+
+ mass = 200;
+
+ VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel );
+ VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
+
+ // set the timer so that the other client can't cancel
+ // out the movement immediately
+ if( !targ->client->ps.pm_time )
+ {
+ int t;
+
+ t = knockback * 2;
+ if( t < 50 )
+ t = 50;
+
+ if( t > 200 )
+ t = 200;
+
+ targ->client->ps.pm_time = t;
+ targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
+ }
+ }
+
+ // check for completely getting out of the damage
+ if( !( dflags & DAMAGE_NO_PROTECTION ) )
+ {
+
+ // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
+ // if the attacker was on the same team
+ if( targ != attacker && OnSameTeam( targ, attacker ) )
+ {
+ if( g_dretchPunt.integer &&
+ targ->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL0 )
+ {
+ vec3_t dir, push;
+
+ VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, dir );
+ VectorNormalizeFast( dir );
+ VectorScale( dir, ( damage * 10.0f ), push );
+ push[2] = 64.0f;
+ VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity );
+ return;
+ }
+ else if(mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE )
+ { // don't do friendly fire on movement attacks
+ if( g_friendlyFireMovementAttacks.value <= 0 || ( g_friendlyFire.value<=0 && g_friendlyFireAliens.value<=0 ) )
+ return;
+ else if( g_friendlyFireMovementAttacks.value > 0 && g_friendlyFireMovementAttacks.value < 1 )
+ damage =(int)(0.5 + g_friendlyFireMovementAttacks.value * (float) damage);
+ }
+ else if( g_friendlyFire.value <=0)
+ {
+ if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if(g_friendlyFireHumans.value<=0)
+ return;
+ else if( g_friendlyFireHumans.value > 0 && g_friendlyFireHumans.value < 1 )
+ damage =(int)(0.5 + g_friendlyFireHumans.value * (float) damage);
+ }
+ if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if(g_friendlyFireAliens.value==0)
+ return;
+ else if( g_friendlyFireAliens.value > 0 && g_friendlyFireAliens.value < 1 )
+ damage =(int)(0.5 + g_friendlyFireAliens.value * (float) damage);
+ }
+ }
+ else if( g_friendlyFire.value > 0 && g_friendlyFire.value < 1 )
+ {
+ damage =(int)(0.5 + g_friendlyFire.value * (float) damage);
+ }
+ }
+
+ // If target is buildable on the same team as the attacking client
+ if( targ->s.eType == ET_BUILDABLE && attacker->client &&
+ targ->biteam == attacker->client->pers.teamSelection )
+ {
+ if(mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE )
+ {
+ if(g_friendlyFireMovementAttacks.value <= 0)
+ return;
+ else if(g_friendlyFireMovementAttacks.value > 0 && g_friendlyFireMovementAttacks.value < 1)
+ damage =(int)(0.5 + g_friendlyFireMovementAttacks.value * (float) damage);
+ }
+ if( g_friendlyBuildableFire.value <= 0 )
+ {
+ return;
+ }
+ else if( g_friendlyBuildableFire.value > 0 && g_friendlyBuildableFire.value < 1 )
+ {
+ damage =(int)(0.5 + g_friendlyBuildableFire.value * (float) damage);
+ }
+ }
+
+ // check for godmode
+ if ( targ->flags & FL_GODMODE && !g_devmapNoGod.integer)
+ return;
+
+ if( level.paused )
+ return;
+
+ if(targ->s.eType == ET_BUILDABLE && g_cheats.integer && g_devmapNoStructDmg.integer)
+ return;
+ }
+
+ // add to the attacker's hit counter
+ if( attacker->client && targ != attacker && targ->health > 0
+ && targ->s.eType != ET_MISSILE
+ && targ->s.eType != ET_GENERAL )
+ {
+ if( OnSameTeam( targ, attacker ) )
+ attacker->client->ps.persistant[ PERS_HITS ]--;
+ else
+ attacker->client->ps.persistant[ PERS_HITS ]++;
+ }
+
+ take = damage;
+ save = 0;
+
+ // add to the damage inflicted on a player this frame
+ // the total will be turned into screen blends and view angle kicks
+ // at the end of the frame
+ if( client )
+ {
+ if( attacker )
+ client->ps.persistant[ PERS_ATTACKER ] = attacker->s.number;
+ else
+ client->ps.persistant[ PERS_ATTACKER ] = ENTITYNUM_WORLD;
+
+ client->damage_armor += asave;
+ client->damage_blood += take;
+ client->damage_knockback += knockback;
+
+ if( dir )
+ {
+ VectorCopy ( dir, client->damage_from );
+ client->damage_fromWorld = qfalse;
+ }
+ else
+ {
+ VectorCopy ( targ->r.currentOrigin, client->damage_from );
+ client->damage_fromWorld = qtrue;
+ }
+
+ // set the last client who damaged the target
+ targ->client->lasthurt_client = attacker->s.number;
+ targ->client->lasthurt_mod = mod;
+
+ damagemodifier = G_CalcDamageModifier( point, targ, attacker, client->ps.stats[ STAT_PCLASS ], dflags );
+ take = (int)( (float)take * damagemodifier );
+
+ //if boosted poison every attack
+ if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED )
+ {
+ if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ !( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) &&
+ mod != MOD_LEVEL2_ZAP &&
+ targ->client->poisonImmunityTime < level.time )
+ {
+ targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED;
+ targ->client->lastPoisonTime = level.time;
+ targ->client->lastPoisonClient = attacker;
+ attacker->client->pers.statscounters.repairspoisons++;
+ level.alienStatsCounters.repairspoisons++;
+ }
+ }
+ }
+
+ if( take < 1 )
+ take = 1;
+
+ if( g_debugDamage.integer )
+ {
+ G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
+ targ->health, take, asave );
+ }
+
+ takeNoOverkill = take;
+ if( takeNoOverkill > targ->health )
+ {
+ if(targ->health > 0)
+ takeNoOverkill = targ->health;
+ else
+ takeNoOverkill = 0;
+ }
+
+ if( take )
+ {
+ //Increment some stats counters
+ if( attacker && attacker->client )
+ {
+ if( targ->biteam == attacker->client->pers.teamSelection || OnSameTeam( targ, attacker ) )
+ {
+ attacker->client->pers.statscounters.ffdmgdone += takeNoOverkill;
+ if( attacker->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.ffdmgdone+=takeNoOverkill;
+ }
+ else if( attacker->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.ffdmgdone+=takeNoOverkill;
+ }
+ }
+ else if( targ->s.eType == ET_BUILDABLE )
+ {
+ attacker->client->pers.statscounters.structdmgdone += takeNoOverkill;
+
+ if( attacker->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.structdmgdone+=takeNoOverkill;
+ }
+ else if( attacker->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.structdmgdone+=takeNoOverkill;
+ }
+
+ if( targ->health > 0 && ( targ->health - take ) <=0 )
+ {
+ attacker->client->pers.statscounters.structskilled++;
+ if( attacker->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.structskilled++;
+ }
+ else if( attacker->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.structskilled++;
+ }
+ }
+ }
+ else if( targ->client )
+ {
+ attacker->client->pers.statscounters.dmgdone +=takeNoOverkill;
+ attacker->client->pers.statscounters.hits++;
+ if( attacker->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.dmgdone+=takeNoOverkill;
+ }
+ else if( attacker->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.dmgdone+=takeNoOverkill;
+ }
+ }
+ }
+
+
+ //Do the damage
+ targ->health = targ->health - take;
+
+ if( targ->client )
+ targ->client->ps.stats[ STAT_HEALTH ] = targ->health;
+
+ targ->lastDamageTime = level.time;
+
+ //TA: add to the attackers "account" on the target
+ if( targ->client && attacker->client )
+ {
+ if( attacker != targ && !OnSameTeam( targ, attacker ) )
+ targ->credits[ attacker->client->ps.clientNum ] += take;
+ else if( attacker != targ && OnSameTeam( targ, attacker ) )
+ targ->client->tkcredits[ attacker->client->ps.clientNum ] += takeNoOverkill;
+ }
+
+ if( targ->health <= 0 )
+ {
+ if( client )
+ targ->flags |= FL_NO_KNOCKBACK;
+
+ if( targ->health < -999 )
+ targ->health = -999;
+
+ targ->enemy = attacker;
+ targ->die( targ, inflictor, attacker, take, mod );
+ return;
+ }
+ else if( targ->pain )
+ targ->pain( targ, attacker, take );
+ }
+}
+
+
+/*
+============
+CanDamage
+
+Returns qtrue if the inflictor can directly damage the target. Used for
+explosions and melee attacks.
+============
+*/
+qboolean CanDamage( gentity_t *targ, vec3_t origin )
+{
+ vec3_t dest;
+ trace_t tr;
+ vec3_t midpoint;
+
+ // use the midpoint of the bounds instead of the origin, because
+ // bmodels may have their origin is 0,0,0
+ VectorAdd( targ->r.absmin, targ->r.absmax, midpoint );
+ VectorScale( midpoint, 0.5, midpoint );
+
+ VectorCopy( midpoint, dest );
+ trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
+ if( tr.fraction == 1.0 || tr.entityNum == targ->s.number )
+ return qtrue;
+
+ // this should probably check in the plane of projection,
+ // rather than in world coordinate, and also include Z
+ VectorCopy( midpoint, dest );
+ dest[ 0 ] += 15.0;
+ dest[ 1 ] += 15.0;
+ trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
+ if( tr.fraction == 1.0 )
+ return qtrue;
+
+ VectorCopy( midpoint, dest );
+ dest[ 0 ] += 15.0;
+ dest[ 1 ] -= 15.0;
+ trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
+ if( tr.fraction == 1.0 )
+ return qtrue;
+
+ VectorCopy( midpoint, dest );
+ dest[ 0 ] -= 15.0;
+ dest[ 1 ] += 15.0;
+ trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
+ if( tr.fraction == 1.0 )
+ return qtrue;
+
+ VectorCopy( midpoint, dest );
+ dest[ 0 ] -= 15.0;
+ dest[ 1 ] -= 15.0;
+ trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
+ if( tr.fraction == 1.0 )
+ return qtrue;
+
+ return qfalse;
+}
+
+
+//TA:
+/*
+============
+G_SelectiveRadiusDamage
+============
+*/
+qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage,
+ float radius, gentity_t *ignore, int mod, int team )
+{
+ float points, dist;
+ gentity_t *ent;
+ int entityList[ MAX_GENTITIES ];
+ int numListedEntities;
+ vec3_t mins, maxs;
+ vec3_t v;
+ vec3_t dir;
+ int i, e;
+ qboolean hitClient = qfalse;
+
+ if( radius < 1 )
+ radius = 1;
+
+ for( i = 0; i < 3; i++ )
+ {
+ mins[ i ] = origin[ i ] - radius;
+ maxs[ i ] = origin[ i ] + radius;
+ }
+
+ numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+
+ for( e = 0; e < numListedEntities; e++ )
+ {
+ ent = &g_entities[ entityList[ e ] ];
+
+ if( ent == ignore )
+ continue;
+
+ if( !ent->takedamage )
+ continue;
+
+ // find the distance from the edge of the bounding box
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ if( origin[ i ] < ent->r.absmin[ i ] )
+ v[ i ] = ent->r.absmin[ i ] - origin[ i ];
+ else if( origin[ i ] > ent->r.absmax[ i ] )
+ v[ i ] = origin[ i ] - ent->r.absmax[ i ];
+ else
+ v[ i ] = 0;
+ }
+
+ dist = VectorLength( v );
+ if( dist >= radius )
+ continue;
+
+ points = damage * ( 1.0 - dist / radius );
+
+ if( CanDamage( ent, origin ) )
+ {
+ VectorSubtract( ent->r.currentOrigin, origin, dir );
+ // push the center of mass higher than the origin so players
+ // get knocked into the air more
+ dir[ 2 ] += 24;
+ G_SelectiveDamage( ent, NULL, attacker, dir, origin,
+ (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod, team );
+ }
+ }
+
+ return hitClient;
+}
+
+
+/*
+============
+G_RadiusDamage
+============
+*/
+qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage,
+ float radius, gentity_t *ignore, int mod )
+{
+ float points, dist;
+ gentity_t *ent;
+ int entityList[ MAX_GENTITIES ];
+ int numListedEntities;
+ vec3_t mins, maxs;
+ vec3_t v;
+ vec3_t dir;
+ int i, e;
+ qboolean hitClient = qfalse;
+
+ if( radius < 1 )
+ radius = 1;
+
+ for( i = 0; i < 3; i++ )
+ {
+ mins[ i ] = origin[ i ] - radius;
+ maxs[ i ] = origin[ i ] + radius;
+ }
+
+ numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+
+ for( e = 0; e < numListedEntities; e++ )
+ {
+ ent = &g_entities[ entityList[ e ] ];
+
+ if( ent == ignore )
+ continue;
+
+ if( !ent->takedamage )
+ continue;
+
+ // find the distance from the edge of the bounding box
+ for( i = 0; i < 3; i++ )
+ {
+ if( origin[ i ] < ent->r.absmin[ i ] )
+ v[ i ] = ent->r.absmin[ i ] - origin[ i ];
+ else if( origin[ i ] > ent->r.absmax[ i ] )
+ v[ i ] = origin[ i ] - ent->r.absmax[ i ];
+ else
+ v[ i ] = 0;
+ }
+
+ dist = VectorLength( v );
+ if( dist >= radius )
+ continue;
+
+ points = damage * ( 1.0 - dist / radius );
+
+ if( CanDamage( ent, origin ) )
+ {
+ VectorSubtract( ent->r.currentOrigin, origin, dir );
+ // push the center of mass higher than the origin so players
+ // get knocked into the air more
+ dir[ 2 ] += 24;
+ G_Damage( ent, NULL, attacker, dir, origin,
+ (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod );
+ }
+ }
+
+ return hitClient;
+}
+
+/*
+============
+G_Knockback
+============
+*/
+void G_Knockback( gentity_t *targ, vec3_t dir, int knockback )
+{
+ if( knockback && targ->client )
+ {
+ vec3_t kvel;
+ float mass;
+
+ mass = 200;
+
+ // Halve knockback for bsuits
+ if( targ->client &&
+ targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ BG_InventoryContainsUpgrade( UP_BATTLESUIT, targ->client->ps.stats ) )
+ mass += 400;
+
+ // Halve knockback for crouching players
+ if(targ->client->ps.pm_flags&PMF_DUCKED) knockback /= 2;
+
+ VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel );
+ VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
+
+ // set the timer so that the other client can't cancel
+ // out the movement immediately
+ if( !targ->client->ps.pm_time )
+ {
+ int t;
+
+ t = knockback * 2;
+ if( t < 50 )
+ t = 50;
+
+ if( t > 200 )
+ t = 200;
+ targ->client->ps.pm_time = t;
+ targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
+ }
+ }
+}
+
diff --git a/src/game/g_local.h b/src/game/g_local.h
new file mode 100644
index 0000000..80e3076
--- /dev/null
+++ b/src/game/g_local.h
@@ -0,0 +1,1533 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// g_local.h -- local definitions for game module
+
+#include "../qcommon/q_shared.h"
+#include "bg_public.h"
+#include "g_public.h"
+
+typedef struct gentity_s gentity_t;
+typedef struct gclient_s gclient_t;
+
+#include "g_admin.h"
+
+//==================================================================
+
+#define INFINITE 1000000
+
+#define FRAMETIME 100 // msec
+#define CARNAGE_REWARD_TIME 3000
+#define REWARD_SPRITE_TIME 2000
+
+#define INTERMISSION_DELAY_TIME 1000
+#define SP_INTERMISSION_DELAY_TIME 5000
+
+// gentity->flags
+#define FL_GODMODE 0x00000010
+#define FL_NOTARGET 0x00000020
+#define FL_TEAMSLAVE 0x00000400 // not the first on the team
+#define FL_NO_KNOCKBACK 0x00000800
+#define FL_DROPPED_ITEM 0x00001000
+#define FL_NO_BOTS 0x00002000 // spawn point not for bot use
+#define FL_NO_HUMANS 0x00004000 // spawn point just for bots
+#define FL_FORCE_GESTURE 0x00008000 // spawn point just for bots
+
+typedef struct
+{
+ qboolean isNB;
+ float Area;
+ float Height;
+} noBuild_t;
+
+typedef struct
+{
+ gentity_t *Marker;
+ vec3_t Origin;
+} nbMarkers_t;
+
+// movers are things like doors, plats, buttons, etc
+typedef enum
+{
+ MOVER_POS1,
+ MOVER_POS2,
+ MOVER_1TO2,
+ MOVER_2TO1,
+
+ ROTATOR_POS1,
+ ROTATOR_POS2,
+ ROTATOR_1TO2,
+ ROTATOR_2TO1,
+
+ MODEL_POS1,
+ MODEL_POS2,
+ MODEL_1TO2,
+ MODEL_2TO1
+} moverState_t;
+
+#define SP_PODIUM_MODEL "models/mapobjects/podium/podium4.md3"
+
+//============================================================================
+
+struct gentity_s
+{
+ entityState_t s; // communicated by server to clients
+ entityShared_t r; // shared by both the server system and game
+
+ // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER
+ // EXPECTS THE FIELDS IN THAT ORDER!
+ //================================
+
+ struct gclient_s *client; // NULL if not a client
+
+ qboolean inuse;
+
+ char *classname; // set in QuakeEd
+ int spawnflags; // set in QuakeEd
+
+ qboolean neverFree; // if true, FreeEntity will only unlink
+ // bodyque uses this
+
+ int flags; // FL_* variables
+
+ char *model;
+ char *model2;
+ int freetime; // level.time when the object was freed
+
+ int eventTime; // events will be cleared EVENT_VALID_MSEC after set
+ qboolean freeAfterEvent;
+ qboolean unlinkAfterEvent;
+
+ qboolean physicsObject; // if true, it can be pushed by movers and fall off edges
+ // all game items are physicsObjects,
+ float physicsBounce; // 1.0 = continuous bounce, 0.0 = no bounce
+ int clipmask; // brushes with this content value will be collided against
+ // when moving. items and corpses do not collide against
+ // players, for instance
+
+ // movers
+ moverState_t moverState;
+ int soundPos1;
+ int sound1to2;
+ int sound2to1;
+ int soundPos2;
+ int soundLoop;
+ gentity_t *parent;
+ gentity_t *nextTrain;
+ gentity_t *prevTrain;
+ vec3_t pos1, pos2;
+ float rotatorAngle;
+ gentity_t *clipBrush; // clipping brush for model doors
+
+ char *message;
+
+ int timestamp; // body queue sinking, etc
+
+ float angle; // set in editor, -1 = up, -2 = down
+ char *target;
+ char *targetname;
+ char *team;
+ char *targetShaderName;
+ char *targetShaderNewName;
+ gentity_t *target_ent;
+
+ float speed;
+ float lastSpeed; // used by trains that have been restarted
+ vec3_t movedir;
+
+ // acceleration evaluation
+ qboolean evaluateAcceleration;
+ vec3_t oldVelocity;
+ vec3_t acceleration;
+ vec3_t oldAccel;
+ vec3_t jerk;
+
+ int nextthink;
+ void (*think)( gentity_t *self );
+ void (*reached)( gentity_t *self ); // movers call this when hitting endpoint
+ void (*blocked)( gentity_t *self, gentity_t *other );
+ void (*touch)( gentity_t *self, gentity_t *other, trace_t *trace );
+ void (*use)( gentity_t *self, gentity_t *other, gentity_t *activator );
+ void (*pain)( gentity_t *self, gentity_t *attacker, int damage );
+ void (*die)( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod );
+
+ int pain_debounce_time;
+ int fly_sound_debounce_time; // wind tunnel
+ int last_move_time;
+
+ int health;
+ int lastHealth; // currently only used for overmind
+
+ qboolean takedamage;
+
+ int damage;
+ int splashDamage; // quad will increase this without increasing radius
+ int splashRadius;
+ int methodOfDeath;
+ int splashMethodOfDeath;
+ int chargeRepeat;
+
+ int count;
+
+ gentity_t *chain;
+ gentity_t *enemy;
+ gentity_t *activator;
+ gentity_t *teamchain; // next entity in team
+ gentity_t *teammaster; // master of the team
+
+ int watertype;
+ int waterlevel;
+
+ int noise_index;
+
+ // timing variables
+ float wait;
+ float random;
+
+ pTeam_t stageTeam;
+ stage_t stageStage;
+
+ int biteam; // buildable item team
+ gentity_t *parentNode; // for creep and defence/spawn dependencies
+ qboolean active; // for power repeater, but could be useful elsewhere
+ qboolean powered; // for human buildables
+ int builtBy; // clientNum of person that built this
+ gentity_t *dccNode; // controlling dcc
+ gentity_t *overmindNode; // controlling overmind
+ qboolean dcced; // controlled by a dcc or not?
+ qboolean spawned; // whether or not this buildable has finished spawning
+ int buildTime; // when this buildable was built
+ int animTime; // last animation change
+ int time1000; // timer evaluated every second
+ qboolean deconstruct; // deconstruct if no BP left
+ int deconstructTime; // time at which structure marked
+ int overmindAttackTimer;
+ int overmindDyingTimer;
+ int overmindSpawnsTimer;
+ int nextPhysicsTime; // buildables don't need to check what they're sitting on
+ // every single frame.. so only do it periodically
+ int clientSpawnTime; // the time until this spawn can spawn a client
+ qboolean lev1Grabbed; // for turrets interacting with lev1s
+ int lev1GrabTime; // for turrets interacting with lev1s
+ int spawnBlockTime;
+
+ int credits[ MAX_CLIENTS ]; // human credits for each client
+ qboolean creditsHash[ MAX_CLIENTS ]; // track who has claimed credit
+ int killedBy; // clientNum of killer
+
+ gentity_t *targeted; // true if the player is currently a valid target of a turret
+ vec3_t turretAim; // aim vector for turrets
+
+ vec4_t animation; // animated map objects
+
+ gentity_t *builder; // occupant of this hovel
+
+ qboolean nonSegModel; // this entity uses a nonsegmented player model
+
+ buildable_t bTriggers[ BA_NUM_BUILDABLES ]; // which buildables are triggers
+ pClass_t cTriggers[ PCL_NUM_CLASSES ]; // which classes are triggers
+ weapon_t wTriggers[ WP_NUM_WEAPONS ]; // which weapons are triggers
+ upgrade_t uTriggers[ UP_NUM_UPGRADES ]; // which upgrades are triggers
+
+ int triggerGravity; // gravity for this trigger
+
+ int suicideTime; // when the client will suicide
+
+ int lastDamageTime;
+
+ int bdnumb; // buildlog entry ID
+
+ // For nobuild!
+ noBuild_t noBuild;
+};
+
+typedef enum
+{
+ CON_DISCONNECTED,
+ CON_CONNECTING,
+ CON_CONNECTED
+} clientConnected_t;
+
+typedef enum
+{
+ SPECTATOR_NOT,
+ SPECTATOR_FREE,
+ SPECTATOR_LOCKED,
+ SPECTATOR_FOLLOW,
+ SPECTATOR_SCOREBOARD
+} spectatorState_t;
+
+typedef enum
+{
+ TEAM_BEGIN, // Beginning a team game, spawn at base
+ TEAM_ACTIVE // Now actively playing
+} playerTeamStateState_t;
+
+typedef struct
+{
+ playerTeamStateState_t state;
+
+ int location;
+
+ int captures;
+ int basedefense;
+ int carrierdefense;
+ int flagrecovery;
+ int fragcarrier;
+ int assists;
+
+ float lasthurtcarrier;
+ float lastreturnedflag;
+ float flagsince;
+ float lastfraggedcarrier;
+} playerTeamState_t;
+
+// the auto following clients don't follow a specific client
+// number, but instead follow the first two active players
+#define FOLLOW_ACTIVE1 -1
+#define FOLLOW_ACTIVE2 -2
+
+// client data that stays across multiple levels or tournament restarts
+// this is achieved by writing all the data to cvar strings at game shutdown
+// time and reading them back at connection time. Anything added here
+// MUST be dealt with in G_InitSessionData() / G_ReadSessionData() / G_WriteSessionData()
+typedef struct
+{
+ team_t sessionTeam;
+ pTeam_t restartTeam; //for !restart keepteams and !restart switchteams
+ int spectatorTime; // for determining next-in-line to play
+ spectatorState_t spectatorState;
+ int spectatorClient; // for chasecam and follow mode
+ int wins, losses; // tournament stats
+ qboolean invisible; // for being invisible on the server - ghosts!
+ qboolean teamLeader; // true when this client is a team leader
+ clientList_t ignoreList;
+} clientSession_t;
+
+#define MAX_NETNAME 36
+
+// data to store details of clients that have abnormally disconnected
+typedef struct connectionRecord_s
+{
+ int clientNum;
+ pTeam_t clientTeam;
+ int clientCredit;
+ int clientScore;
+ int clientEnterTime;
+
+ int ptrCode;
+} connectionRecord_t;
+
+typedef struct
+{
+ short kills;
+ short deaths;
+ short feeds;
+ short suicides;
+ short assists;
+ int dmgdone;
+ int ffdmgdone;
+ int structdmgdone;
+ short structsbuilt;
+ short repairspoisons;
+ short structskilled;
+ int timealive;
+ int timeinbase;
+ short headshots;
+ int hits;
+ int hitslocational;
+ short teamkills;
+ int dretchbasytime;
+ int jetpackusewallwalkusetime;
+ int timeLastViewed;
+ int AllstatstimeLastViewed;
+} statsCounters_t;
+
+typedef struct
+{
+ int kills;
+ int deaths;
+ int feeds;
+ int suicides;
+ int assists;
+ long dmgdone;
+ long ffdmgdone;
+ long structdmgdone;
+ int structsbuilt;
+ int repairspoisons;
+ int structskilled;
+ long timealive;
+ long timeinbase;
+ int headshots;
+ long hits;
+ long hitslocational;
+ int teamkills;
+ long dretchbasytime;
+ long jetpackusewallwalkusetime;
+ long timeLastViewed;
+} statsCounters_level;
+
+// client data that stays across multiple respawns, but is cleared
+// on each level change or team change at ClientBegin()
+typedef struct
+{
+ clientConnected_t connected;
+ usercmd_t cmd; // we would lose angles if not persistant
+ qboolean localClient; // true if "ip" info key is "localhost"
+ qboolean initialSpawn; // the first spawn should be at a cool location
+ qboolean predictItemPickup; // based on cg_predictItems userinfo
+ qboolean pmoveFixed; //
+ char netname[ MAX_NETNAME ];
+ int maxHealth; // for handicapping
+ int enterTime; // level.time the client entered the game
+ playerTeamState_t teamState; // status in teamplay games
+ int voteCount; // to prevent people from constantly calling votes
+ qboolean teamInfo; // send team overlay updates?
+
+ pClass_t classSelection; // player class (copied to ent->client->ps.stats[ STAT_PCLASS ] once spawned)
+ float evolveHealthFraction;
+ weapon_t humanItemSelection; // humans have a starting item
+ pTeam_t teamSelection; // player team (copied to ps.stats[ STAT_PTEAM ])
+
+ int teamChangeTime; // level.time of last team change
+ qboolean joinedATeam; // used to tell when a PTR code is valid
+ connectionRecord_t *connection;
+
+ int nameChangeTime;
+ int nameChanges;
+
+ // used to save playerState_t values while in SPECTATOR_FOLLOW mode
+ int score;
+ int credit;
+ int ping;
+
+ int lastTeamStatus;
+
+ int lastFloodTime; // level.time of last flood-limited command
+ int floodDemerits; // number of flood demerits accumulated
+
+ char lastMessage[ MAX_SAY_TEXT ]; // last message said by this player
+ int lastMessageTime; // level.time of last message said by this player
+
+ int lastTeamKillTime; // level.time of last team kill
+ int teamKillDemerits; // number of team kill demerits accumulated
+
+ vec3_t lastDeathLocation;
+ char guid[ 33 ];
+ char ip[ 16 ];
+ qboolean paused;
+ qboolean muted;
+ int muteExpires; // level.time at which a player is unmuted
+ qboolean ignoreAdminWarnings;
+ qboolean denyBuild;
+ int specExpires; // level.time at which a player can join a team again after being forced into spectator
+ int denyHumanWeapons;
+ int denyAlienClasses;
+ int adminLevel;
+ char adminName[ MAX_NETNAME ];
+ qboolean designatedBuilder;
+ qboolean firstConnect; // This is the first map since connect
+ qboolean useUnlagged;
+ statsCounters_t statscounters;
+} clientPersistant_t;
+
+#define MAX_UNLAGGED_MARKERS 10
+typedef struct unlagged_s {
+ vec3_t origin;
+ vec3_t mins;
+ vec3_t maxs;
+ qboolean used;
+} unlagged_t;
+
+// this structure is cleared on each ClientSpawn(),
+// except for 'client->pers' and 'client->sess'
+struct gclient_s
+{
+ // ps MUST be the first element, because the server expects it
+ playerState_t ps; // communicated by server to clients
+
+ // exported into pmove, but not communicated to clients
+ pmoveExt_t pmext;
+
+ // the rest of the structure is private to game
+ clientPersistant_t pers;
+ clientSession_t sess;
+
+ qboolean readyToExit; // wishes to leave the intermission
+
+ qboolean noclip;
+
+ int lastCmdTime; // level.time of last usercmd_t, for EF_CONNECTION
+ // we can't just use pers.lastCommand.time, because
+ // of the g_sycronousclients case
+ int buttons;
+ int oldbuttons;
+ int latched_buttons;
+
+ vec3_t oldOrigin;
+
+ // sum up damage over an entire frame, so
+ // shotgun blasts give a single big kick
+ int damage_armor; // damage absorbed by armor
+ int damage_blood; // damage taken out of health
+ int damage_knockback; // impact damage
+ vec3_t damage_from; // origin for vector calculation
+ qboolean damage_fromWorld; // if true, don't use the damage_from vector
+
+ //
+ int lastkilled_client;// last client that this client killed
+ int lasthurt_client; // last client that damaged this client
+ int lasthurt_mod; // type of damage the client did
+
+ // timers
+ int respawnTime; // can respawn when time > this
+ int inactivityTime; // kick players when time > this
+ qboolean inactivityWarning;// qtrue if the five seoond warning has been given
+ int rewardTime; // clear the EF_AWARD_IMPRESSIVE, etc when time > this
+
+ int airOutTime;
+
+ int lastKillTime; // for multiple kill rewards
+
+ qboolean fireHeld; // used for hook
+ qboolean fire2Held; // used for alt fire
+ gentity_t *hook; // grapple hook if out
+
+ int switchTeamTime; // time the player switched teams
+
+ // timeResidual is used to handle events that happen every second
+ // like health / armor countdowns and regeneration
+ // two timers, one every 100 msecs, another every sec
+ int time100;
+ int time1000;
+ int time10000;
+
+ char *areabits;
+
+ gentity_t *hovel;
+
+ int lastPoisonTime;
+ int poisonImmunityTime;
+ gentity_t *lastPoisonClient;
+ int lastPoisonCloudedTime;
+ gentity_t *lastPoisonCloudedClient;
+ int grabExpiryTime;
+ int lastLockTime;
+ int lastSlowTime;
+ int lastBoostedTime;
+ int lastMedKitTime;
+ int medKitHealthToRestore;
+ int medKitIncrementTime;
+ int lastCreepSlowTime; // time until creep can be removed
+
+ qboolean allowedToPounce;
+
+ qboolean charging;
+
+ float jetpackfuel;
+
+ vec3_t hovelOrigin; // player origin before entering hovel
+
+ int lastFlameBall; // s.number of the last flame ball fired
+
+#define RAM_FRAMES 1 // number of frames to wait before retriggering
+ int retriggerArmouryMenu; // frame number to retrigger the armoury menu
+
+ unlagged_t unlaggedHist[ MAX_UNLAGGED_MARKERS ];
+ unlagged_t unlaggedBackup;
+ unlagged_t unlaggedCalc;
+ int unlaggedTime;
+
+ int tkcredits[ MAX_CLIENTS ];
+
+};
+
+
+typedef struct spawnQueue_s
+{
+ int clients[ MAX_CLIENTS ];
+
+ int front, back;
+} spawnQueue_t;
+
+#define QUEUE_PLUS1(x) (((x)+1)%MAX_CLIENTS)
+#define QUEUE_MINUS1(x) (((x)+MAX_CLIENTS-1)%MAX_CLIENTS)
+
+void G_InitSpawnQueue( spawnQueue_t *sq );
+int G_GetSpawnQueueLength( spawnQueue_t *sq );
+int G_PopSpawnQueue( spawnQueue_t *sq );
+int G_PeekSpawnQueue( spawnQueue_t *sq );
+qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum );
+qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum );
+qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum );
+int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum );
+
+
+#define MAX_LOCDAMAGE_TEXT 8192
+#define MAX_LOCDAMAGE_REGIONS 16
+
+// store locational damage regions
+typedef struct damageRegion_s
+{
+ float minHeight, maxHeight;
+ int minAngle, maxAngle;
+
+ float modifier;
+
+ qboolean crouch;
+} damageRegion_t;
+
+#define MAX_ARMOUR_TEXT 8192
+#define MAX_ARMOUR_REGIONS 16
+
+// store locational armour regions
+typedef struct armourRegion_s
+{
+ float minHeight, maxHeight;
+ int minAngle, maxAngle;
+
+ float modifier;
+
+ qboolean crouch;
+} armourRegion_t;
+
+//status of the warning of certain events
+typedef enum
+{
+ TW_NOT = 0,
+ TW_IMMINENT,
+ TW_PASSED
+} timeWarning_t;
+
+typedef enum
+{
+ BF_BUILT,
+ BF_DECONNED,
+ BF_DESTROYED,
+ BF_TEAMKILLED
+} buildableFate_t;
+
+// record all changes to the buildable layout - build, decon, destroy - and
+// enough information to revert that change
+typedef struct buildHistory_s buildHistory_t;
+struct buildHistory_s
+{
+ int ID; // persistent ID to aid in specific reverting
+ gentity_t *ent; // who, NULL if they've disconnected (or aren't an ent)
+ char name[ MAX_NETNAME ]; // who, saves name if ent is NULL
+ int buildable; // what
+ vec3_t origin; // where
+ vec3_t angles; // which way round
+ vec3_t origin2; // I don't know what the hell these are, but layoutsave saves
+ vec3_t angles2; // them so I will do the same
+ buildableFate_t fate; // was it built, destroyed or deconned
+ buildHistory_t *next; // next oldest change
+ buildHistory_t *marked; // linked list of markdecon buildings taken
+};
+
+//
+// this structure is cleared as each map is entered
+//
+#define MAX_SPAWN_VARS 64
+#define MAX_SPAWN_VARS_CHARS 4096
+
+typedef struct
+{
+ struct gclient_s *clients; // [maxclients]
+
+ struct gentity_s *gentities;
+ int gentitySize;
+ int num_entities; // current number, <= MAX_GENTITIES
+
+ fileHandle_t logFile;
+
+ // store latched cvars here that we want to get at often
+ int maxclients;
+
+ int framenum;
+ int time; // in msec
+ int previousTime; // so movers can back up when blocked
+ int frameMsec; // trap_Milliseconds() at end frame
+
+ int startTime; // level.time the map was started
+
+ int teamScores[ TEAM_NUM_TEAMS ];
+ int lastTeamLocationTime; // last time of client team location update
+
+ qboolean newSession; // don't use any old session data, because
+ // we changed gametype
+
+ qboolean restarted; // waiting for a map_restart to fire
+
+ int numConnectedClients;
+ int numNonSpectatorClients; // includes connecting clients
+ int numPlayingClients; // connected, non-spectators
+ int sortedClients[MAX_CLIENTS]; // sorted by score
+
+ int numNewbies; // number of UnnamedPlayers who have been renamed this round.
+
+ int snd_fry; // sound index for standing in lava
+
+ // voting state
+ char voteString[MAX_STRING_CHARS];
+ char voteDisplayString[MAX_STRING_CHARS];
+ int votePassThreshold;
+ int voteTime; // level.time vote was called
+ int voteExecuteTime; // time the vote is executed
+ int voteYes;
+ int voteNo;
+ int numVotingClients; // set by CalculateRanks
+
+ // team voting state
+ char teamVoteString[ 2 ][ MAX_STRING_CHARS ];
+ char teamVoteDisplayString[ 2 ][ MAX_STRING_CHARS ];
+ int teamVoteTime[ 2 ]; // level.time vote was called
+ int teamVoteYes[ 2 ];
+ int teamVoteNo[ 2 ];
+ int numteamVotingClients[ 2 ]; // set by CalculateRanks
+
+ // spawn variables
+ qboolean spawning; // the G_Spawn*() functions are valid
+ int numSpawnVars;
+ char *spawnVars[ MAX_SPAWN_VARS ][ 2 ]; // key / value pairs
+ int numSpawnVarChars;
+ char spawnVarChars[ MAX_SPAWN_VARS_CHARS ];
+
+ // intermission state
+ int intermissionQueued; // intermission was qualified, but
+ // wait INTERMISSION_DELAY_TIME before
+ // actually going there so the last
+ // frag can be watched. Disable future
+ // kills during this delay
+ int intermissiontime; // time the intermission was started
+ char *changemap;
+ qboolean readyToExit; // at least one client wants to exit
+ int exitTime;
+ vec3_t intermission_origin; // also used for spectator spawns
+ vec3_t intermission_angle;
+
+ qboolean locationLinked; // target_locations get linked
+ gentity_t *locationHead; // head of the location list
+
+ int numAlienSpawns;
+ int numHumanSpawns;
+
+ int numAlienClients;
+ int numHumanClients;
+
+ float averageNumAlienClients;
+ int numAlienSamples;
+ float averageNumHumanClients;
+ int numHumanSamples;
+
+ int numLiveAlienClients;
+ int numLiveHumanClients;
+
+ int alienBuildPoints;
+ int humanBuildPoints;
+ int humanBuildPointsPowered;
+
+ gentity_t *markedBuildables[ MAX_GENTITIES ];
+ int numBuildablesForRemoval;
+
+ int alienKills;
+ int humanKills;
+
+ qboolean reactorPresent;
+ qboolean overmindPresent;
+ qboolean overmindMuted;
+
+ int humanBaseAttackTimer;
+
+ pTeam_t lastWin;
+
+ int suddenDeathABuildPoints;
+ int suddenDeathHBuildPoints;
+ qboolean suddenDeath;
+ int suddenDeathBeginTime;
+ timeWarning_t suddenDeathWarning;
+ timeWarning_t timelimitWarning;
+ int extend_vote_count;
+
+ spawnQueue_t alienSpawnQueue;
+ spawnQueue_t humanSpawnQueue;
+
+ int alienStage2Time;
+ int alienStage3Time;
+ int humanStage2Time;
+ int humanStage3Time;
+
+ qboolean uncondAlienWin;
+ qboolean uncondHumanWin;
+ qboolean alienTeamLocked;
+ qboolean humanTeamLocked;
+ qboolean paused;
+ int pauseTime;
+ float pause_speed;
+ float pause_gravity;
+ float pause_knockback;
+ int pause_ff;
+ int pause_ffb;
+
+ int lastCreditedAlien;
+ int lastCreditedHuman;
+
+ int unlaggedIndex;
+ int unlaggedTimes[ MAX_UNLAGGED_MARKERS ];
+
+ char layout[ MAX_QPATH ];
+
+ pTeam_t surrenderTeam;
+ buildHistory_t *buildHistory;
+ int lastBuildID;
+ int lastTeamUnbalancedTime;
+ int numTeamWarnings;
+ int lastMsgTime;
+ int mapRotationVoteTime;
+
+ statsCounters_level alienStatsCounters;
+ statsCounters_level humanStatsCounters;
+
+ qboolean noBuilding;
+ float nbArea;
+ float nbHeight;
+
+ nbMarkers_t nbMarkers[ MAX_GENTITIES ];
+} level_locals_t;
+
+#define CMD_CHEAT 0x01
+#define CMD_MESSAGE 0x02 // sends message to others (skip when muted)
+#define CMD_TEAM 0x04 // must be on a team
+#define CMD_NOTEAM 0x08 // must not be on a team
+#define CMD_ALIEN 0x10
+#define CMD_HUMAN 0x20
+#define CMD_LIVING 0x40
+#define CMD_INTERMISSION 0x80 // valid during intermission
+
+typedef struct
+{
+ char *cmdName;
+ int cmdFlags;
+ void ( *cmdHandler )( gentity_t *ent );
+} commands_t;
+
+//
+// g_spawn.c
+//
+qboolean G_SpawnString( const char *key, const char *defaultString, char **out );
+// spawn string returns a temporary reference, you must CopyString() if you want to keep it
+qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out );
+qboolean G_SpawnInt( const char *key, const char *defaultString, int *out );
+qboolean G_SpawnVector( const char *key, const char *defaultString, float *out );
+void G_SpawnEntitiesFromString( void );
+char *G_NewString( const char *string );
+
+//
+// g_cmds.c
+//
+void Cmd_Score_f( gentity_t *ent );
+qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin );
+void G_StopFromFollowing( gentity_t *ent );
+void G_StopFollowing( gentity_t *ent );
+qboolean G_FollowNewClient( gentity_t *ent, int dir );
+void G_ToggleFollow( gentity_t *ent );
+qboolean G_MatchOnePlayer( int *plist, char *err, int len );
+int G_ClientNumbersFromString( char *s, int *plist );
+void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText );
+int G_SayArgc( void );
+qboolean G_SayArgv( int n, char *buffer, int bufferLength );
+char *G_SayConcatArgs( int start );
+void G_DecolorString( char *in, char *out );
+void G_ParseEscapedString( char *buffer );
+void G_LeaveTeam( gentity_t *self );
+void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam );
+void G_SanitiseString( char *in, char *out, int len );
+void G_PrivateMessage( gentity_t *ent );
+char *G_statsString( statsCounters_t *sc, pTeam_t *pt );
+void Cmd_Share_f( gentity_t *ent );
+void Cmd_Donate_f( gentity_t *ent );
+void Cmd_TeamVote_f( gentity_t *ent );
+void Cmd_Builder_f( gentity_t *ent );
+void G_WordWrap( char *buffer, int maxwidth );
+void G_CP( gentity_t *ent );
+qboolean G_IsMuted( gclient_t *ent );
+qboolean G_TeamKill_Repent( gentity_t *ent );
+
+//
+// g_physics.c
+//
+void G_Physics( gentity_t *ent, int msec );
+
+//
+// g_buildable.c
+//
+
+#define MAX_ALIEN_BBOX 25
+
+typedef enum
+{
+ IBE_NONE,
+
+ IBE_NOOVERMIND,
+ IBE_OVERMIND,
+ IBE_NOASSERT,
+ IBE_SPWNWARN,
+ IBE_NOCREEP,
+ IBE_HOVEL,
+ IBE_HOVELEXIT,
+
+ IBE_REACTOR,
+ IBE_REPEATER,
+ IBE_TNODEWARN,
+ IBE_RPTWARN,
+ IBE_RPTWARN2,
+ IBE_NOPOWER,
+ IBE_NODCC,
+
+ IBE_NORMAL,
+ IBE_NOROOM,
+ IBE_PERMISSION,
+
+ IBE_MAXERRORS
+} itemBuildError_t;
+
+qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit );
+gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal,
+ buildable_t spawn, vec3_t spawnOrigin );
+
+qboolean G_IsPowered( vec3_t origin );
+qboolean G_IsDCCBuilt( void );
+qboolean G_IsOvermindBuilt( void );
+
+void G_BuildableThink( gentity_t *ent, int msec );
+qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable );
+itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin );
+qboolean G_BuildingExists( int bclass ) ;
+qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable );
+void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force );
+void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim );
+void G_SpawnBuildable(gentity_t *ent, buildable_t buildable);
+void FinishSpawningBuildable( gentity_t *ent );
+void G_CheckDBProtection( void );
+void G_LayoutSave( char *name );
+int G_LayoutList( const char *map, char *list, int len );
+void G_LayoutSelect( void );
+void G_LayoutLoad( void );
+void G_BaseSelfDestruct( pTeam_t team );
+gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 );
+void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark );
+void G_CommitRevertedBuildable( gentity_t *ent );
+qboolean G_RevertCanFit( buildHistory_t *bh );
+int G_LogBuild( buildHistory_t *new );
+int G_CountBuildLog( void );
+char *G_FindBuildLogName( int id );
+void G_NobuildSave( void );
+void G_NobuildLoad( void );
+
+//
+// g_utils.c
+//
+int G_ParticleSystemIndex( char *name );
+int G_ShaderIndex( char *name );
+int G_ModelIndex( char *name );
+int G_SoundIndex( char *name );
+void G_TeamCommand( pTeam_t team, char *cmd );
+void G_KillBox (gentity_t *ent);
+gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match);
+gentity_t *G_PickTarget (char *targetname);
+void G_UseTargets (gentity_t *ent, gentity_t *activator);
+void G_SetMovedir ( vec3_t angles, vec3_t movedir);
+
+void G_InitGentity( gentity_t *e );
+gentity_t *G_Spawn( void );
+gentity_t *G_TempEntity( vec3_t origin, int event );
+void G_Sound( gentity_t *ent, int channel, int soundIndex );
+void G_FreeEntity( gentity_t *e );
+qboolean G_EntitiesFree( void );
+
+void G_TouchTriggers( gentity_t *ent );
+void G_TouchSolids( gentity_t *ent );
+
+float *tv( float x, float y, float z );
+char *vtos( const vec3_t v );
+
+float vectoyaw( const vec3_t vec );
+
+void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm );
+void G_AddEvent( gentity_t *ent, int event, int eventParm );
+void G_BroadcastEvent( int event, int eventParm );
+void G_SetOrigin( gentity_t *ent, vec3_t origin );
+void AddRemap(const char *oldShader, const char *newShader, float timeOffset);
+const char *BuildShaderStateConfig( void );
+
+
+qboolean G_ClientIsLagging( gclient_t *client );
+
+void G_TriggerMenu( int clientNum, dynMenu_t menu );
+void G_CloseMenus( int clientNum );
+
+qboolean G_Visible( gentity_t *ent1, gentity_t *ent2 );
+gentity_t *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities );
+
+//
+// g_combat.c
+//
+qboolean CanDamage( gentity_t *targ, vec3_t origin );
+void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
+ vec3_t dir, vec3_t point, int damage, int dflags, int mod );
+void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir,
+ vec3_t point, int damage, int dflags, int mod, int team );
+qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius,
+ gentity_t *ignore, int mod );
+qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius,
+ gentity_t *ignore, int mod, int team );
+void G_Knockback( gentity_t *targ, vec3_t dir, int knockback );
+void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
+void AddScore( gentity_t *ent, int score );
+
+void G_InitDamageLocations( void );
+
+// damage flags
+#define DAMAGE_RADIUS 0x00000001 // damage was indirect
+#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage
+#define DAMAGE_NO_KNOCKBACK 0x00000004 // do not affect velocity, just view angles
+#define DAMAGE_NO_PROTECTION 0x00000008 // armor, shields, invulnerability, and godmode have no effect
+#define DAMAGE_NO_LOCDAMAGE 0x00000010 // do not apply locational damage
+
+//
+// g_missile.c
+//
+void G_RunMissile( gentity_t *ent );
+
+gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t aimdir );
+gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius );
+gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir );
+
+
+//
+// g_mover.c
+//
+void G_RunMover( gentity_t *ent );
+void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace );
+void manualTriggerSpectator( gentity_t *trigger, gentity_t *player );
+
+//
+// g_trigger.c
+//
+void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace );
+void G_Checktrigger_stages( pTeam_t team, stage_t stage );
+
+
+//
+// g_misc.c
+//
+void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles );
+void ShineTorch( gentity_t *self );
+
+//
+// g_weapon.c
+//
+
+#define MAX_ZAP_TARGETS LEVEL2_AREAZAP_MAX_TARGETS
+
+typedef struct zap_s
+{
+ qboolean used;
+
+ gentity_t *creator;
+ gentity_t *targets[ MAX_ZAP_TARGETS ];
+ int numTargets;
+
+ int timeToLive;
+ int damageUsed;
+
+ gentity_t *effectChannel;
+} zap_t;
+
+void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon );
+void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo );
+void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint );
+void SnapVectorTowards( vec3_t v, vec3_t to );
+qboolean CheckVenomAttack( gentity_t *ent );
+void CheckGrabAttack( gentity_t *ent );
+qboolean CheckPounceAttack( gentity_t *ent );
+void ChargeAttack( gentity_t *ent, gentity_t *victim );
+void G_UpdateZaps( int msec );
+
+
+//
+// g_client.c
+//
+void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap );
+team_t TeamCount( int ignoreClientNum, int team );
+void G_SetClientViewAngle( gentity_t *ent, vec3_t angle );
+gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles );
+gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles );
+gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles );
+gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles );
+void SpawnCorpse( gentity_t *ent );
+void respawn( gentity_t *ent );
+void BeginIntermission( void );
+void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles );
+void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod );
+qboolean SpotWouldTelefrag( gentity_t *spot );
+char *G_NextNewbieName( gentity_t *ent );
+
+//
+// g_svcmds.c
+//
+qboolean ConsoleCommand( void );
+void G_ProcessIPBans( void );
+qboolean G_FilterPacket( char *from );
+
+//
+// g_weapon.c
+//
+void FireWeapon( gentity_t *ent );
+void FireWeapon2( gentity_t *ent );
+void FireWeapon3( gentity_t *ent );
+
+//
+// g_cmds.c
+//
+
+//
+// g_main.c
+//
+void ScoreboardMessage( gentity_t *client );
+void MoveClientToIntermission( gentity_t *client );
+void G_MapConfigs( const char *mapname );
+void CalculateRanks( void );
+void FindIntermissionPoint( void );
+void G_RunThink( gentity_t *ent );
+void QDECL G_LogPrintf( const char *fmt, ... );
+void QDECL G_LogPrintfColoured( const char *fmt, ... );
+void QDECL G_LogOnlyPrintf( const char *fmt, ... );
+void QDECL G_AdminsPrintf( const char *fmt, ... );
+void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... );
+void QDECL G_LogOnlyPrintf( const char *fmt, ... );
+void SendScoreboardMessageToAllClients( void );
+void QDECL G_Printf( const char *fmt, ... );
+void QDECL G_Error( const char *fmt, ... );
+void CheckVote( void );
+void CheckTeamVote( int teamnum );
+void LogExit( const char *string );
+int G_TimeTilSuddenDeath( void );
+void CheckMsgTimer( void );
+qboolean G_Flood_Limited( gentity_t *ent );
+
+//
+// g_client.c
+//
+char *ClientConnect( int clientNum, qboolean firstTime );
+void ClientUserinfoChanged( int clientNum, qboolean forceName );
+void ClientDisconnect( int clientNum );
+void ClientBegin( int clientNum );
+void ClientCommand( int clientNum );
+
+//
+// g_active.c
+//
+void G_UnlaggedStore( void );
+void G_UnlaggedClear( gentity_t *ent );
+void G_UnlaggedCalc( int time, gentity_t *skipEnt );
+void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range );
+void G_UnlaggedOff( void );
+void ClientThink( int clientNum );
+void ClientEndFrame( gentity_t *ent );
+void G_RunClient( gentity_t *ent );
+
+//
+// g_team.c
+//
+qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 );
+gentity_t *Team_GetLocation( gentity_t *ent );
+qboolean Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen );
+void TeamplayInfoMessage( gentity_t *ent );
+void CheckTeamStatus( void );
+
+//
+// g_mem.c
+//
+void *G_Alloc( int size );
+void G_InitMemory( void );
+void G_Free( void *ptr );
+void G_DefragmentMemory( void );
+void Svcmd_GameMem_f( void );
+
+//
+// g_session.c
+//
+void G_ReadSessionData( gclient_t *client );
+void G_InitSessionData( gclient_t *client, char *userinfo );
+void G_WriteSessionData( void );
+
+//
+// g_maprotation.c
+//
+#define MAX_MAP_ROTATIONS 64
+#define MAX_MAP_ROTATION_MAPS 64
+#define MAX_MAP_COMMANDS 16
+#define MAX_MAP_ROTATION_CONDS 8
+
+#define NOT_ROTATING -1
+
+typedef enum
+{
+ MCV_ERR,
+ MCV_RANDOM,
+ MCV_NUMCLIENTS,
+ MCV_LASTWIN,
+ MCV_VOTE,
+ MCV_SELECTEDRANDOM
+} mapConditionVariable_t;
+
+typedef enum
+{
+ MCO_LT,
+ MCO_EQ,
+ MCO_GT
+} mapConditionOperator_t;
+
+typedef enum
+{
+ MCT_ERR,
+ MCT_MAP,
+ MCT_ROTATION
+} mapConditionType_t;
+
+typedef struct mapRotationCondition_s
+{
+ char dest[ MAX_QPATH ];
+
+ qboolean unconditional;
+
+ mapConditionVariable_t lhs;
+ mapConditionOperator_t op;
+
+ int numClients;
+ pTeam_t lastWin;
+} mapRotationCondition_t;
+
+typedef struct mapRotationEntry_s
+{
+ char name[ MAX_QPATH ];
+
+ char postCmds[ MAX_MAP_COMMANDS ][ MAX_STRING_CHARS ];
+ char layouts[ MAX_CVAR_VALUE_STRING ];
+ int numCmds;
+
+ mapRotationCondition_t conditions[ MAX_MAP_ROTATION_CONDS ];
+ int numConditions;
+} mapRotationEntry_t;
+
+typedef struct mapRotation_s
+{
+ char name[ MAX_QPATH ];
+
+ mapRotationEntry_t maps[ MAX_MAP_ROTATION_MAPS ];
+ int numMaps;
+ int currentMap;
+} mapRotation_t;
+
+typedef struct mapRotations_s
+{
+ mapRotation_t rotations[ MAX_MAP_ROTATIONS ];
+ int numRotations;
+} mapRotations_t;
+
+void G_PrintRotations( void );
+qboolean G_AdvanceMapRotation( void );
+qboolean G_StartMapRotation( char *name, qboolean changeMap );
+void G_StopMapRotation( void );
+qboolean G_MapRotationActive( void );
+void G_InitMapRotations( void );
+qboolean G_MapExists( char *name );
+int G_GetCurrentMap( int rotation );
+
+qboolean G_CheckMapRotationVote( void );
+qboolean G_IntermissionMapVoteWinner( void );
+void G_IntermissionMapVoteMessage( gentity_t *ent );
+void G_IntermissionMapVoteMessageAll( void );
+void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose );
+
+//
+// g_ptr.c
+//
+void G_UpdatePTRConnection( gclient_t *client );
+connectionRecord_t *G_GenerateNewConnection( gclient_t *client );
+void G_ResetPTRConnections( void );
+connectionRecord_t *G_FindConnectionForCode( int code );
+
+
+//some maxs
+#define MAX_FILEPATH 144
+
+extern level_locals_t level;
+extern gentity_t g_entities[ MAX_GENTITIES ];
+
+#define FOFS(x) ((int)&(((gentity_t *)0)->x))
+
+extern vmCvar_t g_dedicated;
+extern vmCvar_t g_cheats;
+extern vmCvar_t g_maxclients; // allow this many total, including spectators
+extern vmCvar_t g_maxGameClients; // allow this many active
+extern vmCvar_t g_restarted;
+extern vmCvar_t g_lockTeamsAtStart;
+extern vmCvar_t g_minCommandPeriod;
+extern vmCvar_t g_minNameChangePeriod;
+extern vmCvar_t g_maxNameChanges;
+extern vmCvar_t g_newbieNumbering;
+extern vmCvar_t g_newbieNamePrefix;
+
+extern vmCvar_t g_timelimit;
+extern vmCvar_t g_suddenDeathTime;
+extern vmCvar_t g_suddenDeath;
+extern vmCvar_t g_suddenDeathMode;
+extern vmCvar_t g_friendlyFire;
+extern vmCvar_t g_friendlyFireHumans;
+extern vmCvar_t g_friendlyFireAliens;
+extern vmCvar_t g_retribution;
+extern vmCvar_t g_friendlyFireMovementAttacks;
+extern vmCvar_t g_friendlyBuildableFire;
+extern vmCvar_t g_password;
+extern vmCvar_t g_needpass;
+extern vmCvar_t g_gravity;
+extern vmCvar_t g_speed;
+extern vmCvar_t g_knockback;
+extern vmCvar_t g_quadfactor;
+extern vmCvar_t g_inactivity;
+extern vmCvar_t g_debugMove;
+extern vmCvar_t g_debugAlloc;
+extern vmCvar_t g_debugDamage;
+extern vmCvar_t g_weaponRespawn;
+extern vmCvar_t g_weaponTeamRespawn;
+extern vmCvar_t g_synchronousClients;
+extern vmCvar_t g_motd;
+extern vmCvar_t g_warmup;
+extern vmCvar_t g_warmupMode;
+extern vmCvar_t g_doWarmup;
+extern vmCvar_t g_blood;
+extern vmCvar_t g_allowVote;
+extern vmCvar_t g_requireVoteReasons;
+extern vmCvar_t g_voteLimit;
+extern vmCvar_t g_suddenDeathVotePercent;
+extern vmCvar_t g_suddenDeathVoteDelay;
+extern vmCvar_t g_extendVotesPercent;
+extern vmCvar_t g_extendVotesTime;
+extern vmCvar_t g_extendVotesCount;
+extern vmCvar_t g_kickVotesPercent;
+extern vmCvar_t g_customVote1;
+extern vmCvar_t g_customVote2;
+extern vmCvar_t g_customVote3;
+extern vmCvar_t g_customVote4;
+extern vmCvar_t g_customVote5;
+extern vmCvar_t g_customVote6;
+extern vmCvar_t g_customVote7;
+extern vmCvar_t g_customVote8;
+#define CUSTOM_VOTE_COUNT 8
+extern vmCvar_t g_customVotePercent;
+extern vmCvar_t g_mapVotesPercent;
+extern vmCvar_t g_mapRotationVote;
+extern vmCvar_t g_extendVotesPercent;
+extern vmCvar_t g_extendVotesTime;
+extern vmCvar_t g_extendVotesCount;
+extern vmCvar_t g_readyPercent;
+extern vmCvar_t g_designateVotes;
+extern vmCvar_t g_teamAutoJoin;
+extern vmCvar_t g_teamForceBalance;
+extern vmCvar_t g_banIPs;
+extern vmCvar_t g_filterBan;
+extern vmCvar_t g_smoothClients;
+extern vmCvar_t g_clientUpgradeNotice;
+extern vmCvar_t pmove_fixed;
+extern vmCvar_t pmove_msec;
+extern vmCvar_t g_rankings;
+extern vmCvar_t g_allowShare;
+extern vmCvar_t g_creditOverflow;
+extern vmCvar_t g_enableDust;
+extern vmCvar_t g_enableBreath;
+extern vmCvar_t g_singlePlayer;
+
+extern vmCvar_t g_humanBuildPoints;
+extern vmCvar_t g_alienBuildPoints;
+extern vmCvar_t g_humanStage;
+extern vmCvar_t g_humanKills;
+extern vmCvar_t g_humanMaxStage;
+extern vmCvar_t g_humanStage2Threshold;
+extern vmCvar_t g_humanStage3Threshold;
+extern vmCvar_t g_alienStage;
+extern vmCvar_t g_alienKills;
+extern vmCvar_t g_alienMaxStage;
+extern vmCvar_t g_alienStage2Threshold;
+extern vmCvar_t g_alienStage3Threshold;
+extern vmCvar_t g_teamImbalanceWarnings;
+
+extern vmCvar_t g_unlagged;
+
+extern vmCvar_t g_disabledEquipment;
+extern vmCvar_t g_disabledClasses;
+extern vmCvar_t g_disabledBuildables;
+
+extern vmCvar_t g_markDeconstruct;
+extern vmCvar_t g_markDeconstructMode;
+extern vmCvar_t g_deconDead;
+
+extern vmCvar_t g_debugMapRotation;
+extern vmCvar_t g_currentMapRotation;
+extern vmCvar_t g_currentMap;
+extern vmCvar_t g_nextMap;
+extern vmCvar_t g_initialMapRotation;
+extern vmCvar_t g_chatTeamPrefix;
+extern vmCvar_t g_actionPrefix;
+extern vmCvar_t g_floodMaxDemerits;
+extern vmCvar_t g_floodMinTime;
+extern vmCvar_t g_spamTime;
+
+extern vmCvar_t g_shove;
+
+extern vmCvar_t g_mapConfigs;
+
+extern vmCvar_t g_layouts;
+extern vmCvar_t g_layoutAuto;
+
+extern vmCvar_t g_admin;
+extern vmCvar_t g_adminLog;
+extern vmCvar_t g_adminParseSay;
+extern vmCvar_t g_adminSayFilter;
+extern vmCvar_t g_adminNameProtect;
+extern vmCvar_t g_adminTempMute;
+extern vmCvar_t g_adminTempBan;
+extern vmCvar_t g_adminMaxBan;
+extern vmCvar_t g_adminTempSpec;
+extern vmCvar_t g_adminMapLog;
+extern vmCvar_t g_minLevelToJoinTeam;
+extern vmCvar_t g_minDeconLevel;
+extern vmCvar_t g_minDeconAffectsMark;
+extern vmCvar_t g_forceAutoSelect;
+extern vmCvar_t g_minLevelToSpecMM1;
+extern vmCvar_t g_banNotice;
+
+extern vmCvar_t g_devmapKillerHP;
+extern vmCvar_t g_killerHP;
+
+extern vmCvar_t g_privateMessages;
+extern vmCvar_t g_fullIgnore;
+extern vmCvar_t g_decolourLogfiles;
+extern vmCvar_t g_publicSayadmins;
+extern vmCvar_t g_myStats;
+extern vmCvar_t g_teamStatus;
+extern vmCvar_t g_antiSpawnBlock;
+
+extern vmCvar_t g_dretchPunt;
+
+extern vmCvar_t g_devmapNoGod;
+extern vmCvar_t g_devmapNoStructDmg;
+
+extern vmCvar_t g_slapKnockback;
+extern vmCvar_t g_slapDamage;
+
+extern vmCvar_t g_voteMinTime;
+extern vmCvar_t g_mapvoteMaxTime;
+extern vmCvar_t g_votableMaps;
+
+extern vmCvar_t g_msg;
+extern vmCvar_t g_msgTime;
+extern vmCvar_t g_welcomeMsg;
+extern vmCvar_t g_welcomeMsgTime;
+extern vmCvar_t g_deconBanTime;
+
+extern vmCvar_t g_buildLogMaxLength;
+
+extern vmCvar_t g_AllStats;
+extern vmCvar_t g_AllStatsTime;
+
+extern vmCvar_t mod_jetpackFuel;
+extern vmCvar_t mod_jetpackConsume;
+extern vmCvar_t mod_jetpackRegen;
+
+extern vmCvar_t g_adminExpireTime;
+
+extern vmCvar_t g_autoGhost;
+
+extern vmCvar_t g_teamKillThreshold;
+
+extern vmCvar_t g_aimbotAdvertBan;
+extern vmCvar_t g_aimbotAdvertBanTime;
+extern vmCvar_t g_aimbotAdvertBanReason;
+
+void trap_Printf( const char *fmt );
+void trap_Error( const char *fmt );
+int trap_Milliseconds( void );
+int trap_RealTime( qtime_t *qtime );
+int trap_Argc( void );
+void trap_Argv( int n, char *buffer, int bufferLength );
+void trap_Args( char *buffer, int bufferLength );
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
+void trap_FS_Read( void *buffer, int len, fileHandle_t f );
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f );
+void trap_FS_FCloseFile( fileHandle_t f );
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
+int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t
+void trap_SendConsoleCommand( int exec_when, const char *text );
+void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags );
+void trap_Cvar_Update( vmCvar_t *cvar );
+void trap_Cvar_Set( const char *var_name, const char *value );
+int trap_Cvar_VariableIntegerValue( const char *var_name );
+float trap_Cvar_VariableValue( const char *var_name );
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+void trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t,
+ playerState_t *gameClients, int sizeofGameClient );
+void trap_DropClient( int clientNum, const char *reason );
+void trap_SendServerCommand( int clientNum, const char *text );
+void trap_SetConfigstring( int num, const char *string );
+void trap_GetConfigstring( int num, char *buffer, int bufferSize );
+void trap_GetUserinfo( int num, char *buffer, int bufferSize );
+void trap_SetUserinfo( int num, const char *buffer );
+void trap_GetServerinfo( char *buffer, int bufferSize );
+void trap_SetBrushModel( gentity_t *ent, const char *name );
+void trap_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs,
+ const vec3_t end, int passEntityNum, int contentmask );
+int trap_PointContents( const vec3_t point, int passEntityNum );
+qboolean trap_InPVS( const vec3_t p1, const vec3_t p2 );
+qboolean trap_InPVSIgnorePortals( const vec3_t p1, const vec3_t p2 );
+void trap_AdjustAreaPortalState( gentity_t *ent, qboolean open );
+qboolean trap_AreasConnected( int area1, int area2 );
+void trap_LinkEntity( gentity_t *ent );
+void trap_UnlinkEntity( gentity_t *ent );
+int trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount );
+qboolean trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent );
+int trap_BotAllocateClient( void );
+void trap_BotFreeClient( int clientNum );
+void trap_GetUsercmd( int clientNum, usercmd_t *cmd );
+qboolean trap_GetEntityToken( char *buffer, int bufferSize );
+
+void trap_SnapVector( float *v );
+void trap_SendGameStat( const char *data );
diff --git a/src/game/g_main.c b/src/game/g_main.c
new file mode 100644
index 0000000..4299107
--- /dev/null
+++ b/src/game/g_main.c
@@ -0,0 +1,3003 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+#define QVM_NAME "AA-QVM"
+#define QVM_VERSIONNUM "MULTIPROTOCOL"
+
+level_locals_t level;
+
+typedef struct
+{
+ vmCvar_t *vmCvar;
+ char *cvarName;
+ char *defaultString;
+ int cvarFlags;
+ int modificationCount; // for tracking changes
+ qboolean trackChange; // track this variable, and announce if changed
+ qboolean teamShader; // track and if changed, update shader state
+} cvarTable_t;
+
+gentity_t g_entities[ MAX_GENTITIES ];
+gclient_t g_clients[ MAX_CLIENTS ];
+
+vmCvar_t g_fraglimit;
+vmCvar_t g_timelimit;
+vmCvar_t g_suddenDeathTime;
+vmCvar_t g_suddenDeath;
+vmCvar_t g_suddenDeathMode;
+vmCvar_t g_capturelimit;
+vmCvar_t g_friendlyFire;
+vmCvar_t g_friendlyFireAliens;
+vmCvar_t g_friendlyFireHumans;
+vmCvar_t g_friendlyFireMovementAttacks;
+vmCvar_t g_retribution;
+vmCvar_t g_friendlyBuildableFire;
+vmCvar_t g_password;
+vmCvar_t g_needpass;
+vmCvar_t g_maxclients;
+vmCvar_t g_maxGameClients;
+vmCvar_t g_dedicated;
+vmCvar_t g_speed;
+vmCvar_t g_gravity;
+vmCvar_t g_cheats;
+vmCvar_t g_knockback;
+vmCvar_t g_quadfactor;
+vmCvar_t g_inactivity;
+vmCvar_t g_debugMove;
+vmCvar_t g_debugDamage;
+vmCvar_t g_debugAlloc;
+vmCvar_t g_weaponRespawn;
+vmCvar_t g_weaponTeamRespawn;
+vmCvar_t g_motd;
+vmCvar_t g_synchronousClients;
+vmCvar_t g_warmup;
+vmCvar_t g_warmupMode;
+vmCvar_t g_doWarmup;
+vmCvar_t g_restarted;
+vmCvar_t g_lockTeamsAtStart;
+vmCvar_t g_logFile;
+vmCvar_t g_logFileSync;
+vmCvar_t g_blood;
+vmCvar_t g_podiumDist;
+vmCvar_t g_podiumDrop;
+vmCvar_t g_allowVote;
+vmCvar_t g_requireVoteReasons;
+vmCvar_t g_voteLimit;
+vmCvar_t g_suddenDeathVotePercent;
+vmCvar_t g_suddenDeathVoteDelay;
+vmCvar_t g_extendVotesPercent;
+vmCvar_t g_extendVotesTime;
+vmCvar_t g_extendVotesCount;
+vmCvar_t g_kickVotesPercent;
+vmCvar_t g_customVote1;
+vmCvar_t g_customVote2;
+vmCvar_t g_customVote3;
+vmCvar_t g_customVote4;
+vmCvar_t g_customVote5;
+vmCvar_t g_customVote6;
+vmCvar_t g_customVote7;
+vmCvar_t g_customVote8;
+vmCvar_t g_customVotePercent;
+vmCvar_t g_mapVotesPercent;
+vmCvar_t g_extendVotesPercent;
+vmCvar_t g_extendVotesTime;
+vmCvar_t g_extendVotesCount;
+vmCvar_t g_mapRotationVote;
+vmCvar_t g_readyPercent;
+vmCvar_t g_designateVotes;
+vmCvar_t g_teamAutoJoin;
+vmCvar_t g_teamForceBalance;
+vmCvar_t g_banIPs;
+vmCvar_t g_filterBan;
+vmCvar_t g_smoothClients;
+vmCvar_t g_clientUpgradeNotice;
+vmCvar_t pmove_fixed;
+vmCvar_t pmove_msec;
+vmCvar_t g_rankings;
+vmCvar_t g_listEntity;
+vmCvar_t g_minCommandPeriod;
+vmCvar_t g_minNameChangePeriod;
+vmCvar_t g_maxNameChanges;
+vmCvar_t g_newbieNumbering;
+vmCvar_t g_newbieNamePrefix;
+
+vmCvar_t g_humanBuildPoints;
+vmCvar_t g_alienBuildPoints;
+vmCvar_t g_humanStage;
+vmCvar_t g_humanKills;
+vmCvar_t g_humanMaxStage;
+vmCvar_t g_humanStage2Threshold;
+vmCvar_t g_humanStage3Threshold;
+vmCvar_t g_alienStage;
+vmCvar_t g_alienKills;
+vmCvar_t g_alienMaxStage;
+vmCvar_t g_alienStage2Threshold;
+vmCvar_t g_alienStage3Threshold;
+vmCvar_t g_teamImbalanceWarnings;
+
+vmCvar_t g_unlagged;
+
+vmCvar_t g_disabledEquipment;
+vmCvar_t g_disabledClasses;
+vmCvar_t g_disabledBuildables;
+
+vmCvar_t g_markDeconstruct;
+vmCvar_t g_markDeconstructMode;
+vmCvar_t g_deconDead;
+
+vmCvar_t g_debugMapRotation;
+vmCvar_t g_currentMapRotation;
+vmCvar_t g_currentMap;
+vmCvar_t g_nextMap;
+vmCvar_t g_initialMapRotation;
+
+vmCvar_t g_shove;
+
+vmCvar_t g_mapConfigs;
+vmCvar_t g_chatTeamPrefix;
+vmCvar_t g_actionPrefix;
+vmCvar_t g_floodMaxDemerits;
+vmCvar_t g_floodMinTime;
+vmCvar_t g_spamTime;
+
+vmCvar_t g_layouts;
+vmCvar_t g_layoutAuto;
+
+vmCvar_t g_admin;
+vmCvar_t g_adminLog;
+vmCvar_t g_adminParseSay;
+vmCvar_t g_adminSayFilter;
+vmCvar_t g_adminNameProtect;
+vmCvar_t g_adminTempMute;
+vmCvar_t g_adminTempBan;
+vmCvar_t g_adminMaxBan;
+vmCvar_t g_adminTempSpec;
+vmCvar_t g_adminMapLog;
+vmCvar_t g_minLevelToJoinTeam;
+vmCvar_t g_minDeconLevel;
+vmCvar_t g_minDeconAffectsMark;
+vmCvar_t g_forceAutoSelect;
+
+vmCvar_t g_privateMessages;
+vmCvar_t g_fullIgnore;
+vmCvar_t g_decolourLogfiles;
+vmCvar_t g_minLevelToSpecMM1;
+vmCvar_t g_publicSayadmins;
+vmCvar_t g_myStats;
+vmCvar_t g_AllStats;
+vmCvar_t g_AllStatsTime;
+vmCvar_t g_teamStatus;
+vmCvar_t g_antiSpawnBlock;
+vmCvar_t g_banNotice;
+
+vmCvar_t g_devmapKillerHP;
+vmCvar_t g_killerHP;
+
+vmCvar_t g_buildLogMaxLength;
+
+vmCvar_t g_tag;
+
+vmCvar_t g_dretchPunt;
+
+vmCvar_t g_allowShare;
+vmCvar_t g_creditOverflow;
+
+vmCvar_t g_devmapNoGod;
+vmCvar_t g_devmapNoStructDmg;
+
+vmCvar_t g_slapKnockback;
+vmCvar_t g_slapDamage;
+
+vmCvar_t g_voteMinTime;
+vmCvar_t g_mapvoteMaxTime;
+vmCvar_t g_votableMaps;
+
+vmCvar_t g_msg;
+vmCvar_t g_msgTime;
+vmCvar_t g_welcomeMsg;
+vmCvar_t g_welcomeMsgTime;
+vmCvar_t g_deconBanTime;
+
+
+vmCvar_t mod_jetpackFuel;
+vmCvar_t mod_jetpackConsume;
+vmCvar_t mod_jetpackRegen;
+
+vmCvar_t g_adminExpireTime;
+
+vmCvar_t g_autoGhost;
+
+vmCvar_t g_teamKillThreshold;
+
+vmCvar_t g_aimbotAdvertBan;
+vmCvar_t g_aimbotAdvertBanTime;
+vmCvar_t g_aimbotAdvertBanReason;
+
+static cvarTable_t gameCvarTable[ ] =
+{
+ // don't override the cheat state set by the system
+ { &g_cheats, "sv_cheats", "", 0, 0, qfalse },
+
+ // noset vars
+ { NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
+ { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse },
+ { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse },
+ { &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse },
+ { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
+ { NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
+ { NULL, "ff", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
+ { NULL, "qvm_version", QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
+
+ // latched vars
+
+ { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
+ { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
+
+ // change anytime vars
+ { &g_timelimit, "timelimit", "45", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
+ { &g_suddenDeathTime, "g_suddenDeathTime", "30", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
+ { &g_suddenDeathMode, "g_suddenDeathMode", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
+ { &g_suddenDeath, "g_suddenDeath", "0", CVAR_SERVERINFO | CVAR_NORESTART, 0, qtrue },
+
+ { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse },
+
+ { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
+ { &g_friendlyFireAliens, "g_friendlyFireAliens", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_friendlyFireHumans, "g_friendlyFireHumans", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_retribution, "g_retribution", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
+ { &g_friendlyFireMovementAttacks, "g_friendlyFireMovementAttacks", "1", CVAR_ARCHIVE, 0, qtrue },
+ { &g_devmapNoGod, "g_devmapNoGod", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_devmapNoStructDmg, "g_devmapNoStructDmg", "0", CVAR_ARCHIVE, 0, qtrue },
+
+ { &g_slapKnockback, "g_slapKnockback", "200", CVAR_ARCHIVE, 0, qfalse},
+ { &g_slapDamage, "g_slapDamage", "0", CVAR_ARCHIVE, 0, qfalse},
+
+ { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE },
+ { &g_teamForceBalance, "g_teamForceBalance", "1", CVAR_ARCHIVE },
+
+ { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue },
+ { &g_warmupMode, "g_warmupMode", "1", CVAR_ARCHIVE, 0, qtrue },
+ { &g_doWarmup, "g_doWarmup", "1", CVAR_ARCHIVE, 0, qtrue },
+ { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse },
+ { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse },
+
+ { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
+
+ { &g_autoGhost, "g_autoGhost", "1", CVAR_SERVERINFO, 0, qfalse },
+
+ { &g_dedicated, "dedicated", "0", 0, 0, qfalse },
+
+ { &g_speed, "g_speed", "320", CVAR_SERVERINFO, 0, qtrue },
+ { &g_gravity, "g_gravity", "800", CVAR_SERVERINFO, 0, qtrue },
+ { &g_knockback, "g_knockback", "1000", CVAR_SERVERINFO, 0, qtrue },
+ { &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue },
+ { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue },
+ { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
+ { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },
+ { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
+ { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse },
+ { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
+ { &g_motd, "g_motd", "", 0, 0, qfalse },
+ { &g_blood, "com_blood", "1", 0, 0, qfalse },
+
+ { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
+ { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },
+
+ { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse },
+ { &g_requireVoteReasons, "g_requireVoteReasons", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse },
+ { &g_voteMinTime, "g_voteMinTime", "120", CVAR_ARCHIVE, 0, qfalse },
+ { &g_mapvoteMaxTime, "g_mapvoteMaxTime", "240", CVAR_ARCHIVE, 0, qfalse },
+ { &g_votableMaps, "g_votableMaps", "", CVAR_ARCHIVE, 0, qtrue },
+ { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse },
+ { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote1, "g_customVote1", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote2, "g_customVote2", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote3, "g_customVote3", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote4, "g_customVote4", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote5, "g_customVote5", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote6, "g_customVote6", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote7, "g_customVote7", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVote8, "g_customVote8", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_customVotePercent, "g_customVotePercent", "50", CVAR_ARCHIVE, 0, qfalse },
+ { &g_mapVotesPercent, "g_mapVotesPercent", "50", CVAR_ARCHIVE, 0, qfalse },
+ { &g_extendVotesPercent, "g_extendVotesPercent", "74", CVAR_ARCHIVE, 0, qfalse },
+ { &g_extendVotesTime, "g_extendVotesTime", "10", CVAR_ARCHIVE, 0, qfalse },
+ { &g_extendVotesCount, "g_extendVotesCount", "2", CVAR_ARCHIVE, 0, qfalse },
+ { &g_mapRotationVote, "g_mapRotationVote", "15", CVAR_ARCHIVE, 0, qfalse },
+ { &g_readyPercent, "g_readyPercent", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_designateVotes, "g_designateVotes", "0", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },
+ { &g_minCommandPeriod, "g_minCommandPeriod", "500", 0, 0, qfalse},
+ { &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse},
+ { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse},
+ { &g_newbieNumbering, "g_newbieNumbering", "0", CVAR_ARCHIVE, 0, qfalse},
+ { &g_newbieNamePrefix, "g_newbieNamePrefix", "Newbie#", CVAR_ARCHIVE, 0, qfalse},
+
+ { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
+ { &g_clientUpgradeNotice, "g_clientUpgradeNotice", "1", 0, 0, qfalse},
+ { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
+ { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
+
+ { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse },
+ { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse },
+ { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse },
+ { &g_humanKills, "g_humanKills", "0", 0, 0, qfalse },
+ { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse },
+ { &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0, qfalse },
+ { &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0, qfalse },
+ { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse },
+ { &g_alienKills, "g_alienKills", "0", 0, 0, qfalse },
+ { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse },
+ { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse },
+ { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse },
+
+ { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue },
+
+ { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse },
+ { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse },
+ { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM, 0, qfalse },
+
+ { &g_chatTeamPrefix, "g_chatTeamPrefix", "1", CVAR_ARCHIVE },
+ { &g_actionPrefix, "g_actionPrefix", "* ", CVAR_ARCHIVE, 0, qfalse },
+ { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse },
+ { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse },
+ { &g_spamTime, "g_spamTime", "2", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_markDeconstruct, "g_markDeconstruct", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_markDeconstructMode, "g_markDeconstructMode", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_deconDead, "g_deconDead", "0", CVAR_ARCHIVE, 0, qtrue },
+
+ { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse },
+ { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING
+ { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse },
+ { &g_nextMap, "g_nextMap", "", 0 , 0, qtrue },
+ { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_shove, "g_shove", "15", CVAR_ARCHIVE, 0, qfalse },
+ { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse },
+ { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse },
+
+ { &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse },
+ { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminLog, "g_adminLog", "admin.log", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminSayFilter, "g_adminSayFilter", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminTempMute, "g_adminTempMute", "5m", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminTempSpec, "g_adminTempSpec", "2m", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminMapLog, "g_adminMapLog", "", CVAR_ROM, 0, qfalse },
+ { &g_minLevelToJoinTeam, "g_minLevelToJoinTeam", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_minDeconLevel, "g_minDeconLevel", "0", CVAR_ARCHIVE, 0, qfalse},
+ { &g_minDeconAffectsMark, "g_minDeconAffectsMark", "0", CVAR_ARCHIVE, 0, qfalse},
+ { &g_forceAutoSelect, "g_forceAutoSelect", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_adminExpireTime, "g_adminExpireTime", "0", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse },
+ { &g_fullIgnore, "g_fullIgnore", "1", CVAR_ARCHIVE, 0, qtrue },
+ { &g_decolourLogfiles, "g_decolourLogfiles", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_buildLogMaxLength, "g_buildLogMaxLength", "50", CVAR_ARCHIVE, 0, qfalse },
+ { &g_myStats, "g_myStats", "1", CVAR_ARCHIVE, 0, qtrue },
+ { &g_AllStats, "g_AllStats", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_AllStatsTime, "g_AllStatsTime", "60", CVAR_ARCHIVE, 0, qfalse },
+ { &g_teamStatus, "g_teamStatus", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_publicSayadmins, "g_publicSayadmins", "1", CVAR_ARCHIVE, 0, qfalse },
+ { &g_minLevelToSpecMM1, "g_minLevelToSpecMM1", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_antiSpawnBlock, "g_antiSpawnBlock", "0", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_devmapKillerHP, "g_devmapKillerHP", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_killerHP, "g_killerHP", "0", CVAR_ARCHIVE, 0, qtrue },
+
+ { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse },
+
+ { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_msg, "g_msg", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_msgTime, "g_msgTime", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_welcomeMsg, "g_welcomeMsg", "", CVAR_ARCHIVE, 0, qfalse },
+ { &g_welcomeMsgTime, "g_welcomeMsgTime", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_deconBanTime, "g_deconBanTime", "2h", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_rankings, "g_rankings", "0", 0, 0, qfalse },
+ { &g_allowShare, "g_allowShare", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse},
+ { &g_creditOverflow, "g_creditOverflow", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse},
+ { &g_banNotice, "g_banNotice", "", CVAR_ARCHIVE, 0, qfalse },
+
+ { &mod_jetpackFuel, "mod_jetpackFuel", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &mod_jetpackConsume, "mod_jetpackConsume", "2", CVAR_ARCHIVE, 0, qfalse },
+ { &mod_jetpackRegen, "mod_jetpackRegen", "3", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_teamKillThreshold, "g_teamKillThreshold", "0", CVAR_ARCHIVE, 0, qfalse },
+
+ { &g_aimbotAdvertBan, "g_aimbotAdvertBan", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_aimbotAdvertBanTime, "g_aimbotAdvertBanTime", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_aimbotAdvertBanReason, "g_aimbotAdvertBanReason", "AUTOBAN: AIMBOT", CVAR_ARCHIVE, 0, qfalse }
+};
+
+static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] );
+
+
+void G_InitGame( int levelTime, int randomSeed, int restart );
+void G_RunFrame( int levelTime );
+void G_ShutdownGame( int restart );
+void CheckExitRules( void );
+
+void G_CountSpawns( void );
+void G_CalculateBuildPoints( void );
+
+/*
+================
+vmMain
+
+This is the only way control passes into the module.
+This must be the very first function compiled into the .q3vm file
+================
+*/
+Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4,
+ int arg5, int arg6, int arg7, int arg8, int arg9,
+ int arg10, int arg11 )
+{
+ switch( command )
+ {
+ case GAME_INIT:
+ G_InitGame( arg0, arg1, arg2 );
+ return 0;
+
+ case GAME_SHUTDOWN:
+ G_ShutdownGame( arg0 );
+ return 0;
+
+ case GAME_CLIENT_CONNECT:
+ return (intptr_t)ClientConnect( arg0, arg1 );
+
+ case GAME_CLIENT_THINK:
+ ClientThink( arg0 );
+ return 0;
+
+ case GAME_CLIENT_USERINFO_CHANGED:
+ ClientUserinfoChanged( arg0, qfalse );
+ return 0;
+
+ case GAME_CLIENT_DISCONNECT:
+ ClientDisconnect( arg0 );
+ return 0;
+
+ case GAME_CLIENT_BEGIN:
+ ClientBegin( arg0 );
+ return 0;
+
+ case GAME_CLIENT_COMMAND:
+ ClientCommand( arg0 );
+ return 0;
+
+ case GAME_RUN_FRAME:
+ G_RunFrame( arg0 );
+ return 0;
+
+ case GAME_CONSOLE_COMMAND:
+ return ConsoleCommand( );
+ }
+
+ return -1;
+}
+
+
+void QDECL G_Printf( const char *fmt, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, fmt );
+ vsprintf( text, fmt, argptr );
+ va_end( argptr );
+
+ trap_Printf( text );
+}
+
+void QDECL G_Error( const char *fmt, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, fmt );
+ vsprintf( text, fmt, argptr );
+ va_end( argptr );
+
+ trap_Error( text );
+}
+
+/*
+================
+G_FindTeams
+
+Chain together all entities with a matching team field.
+Entity teams are used for item groups and multi-entity mover groups.
+
+All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
+All but the last will have the teamchain field set to the next one
+================
+*/
+void G_FindTeams( void )
+{
+ gentity_t *e, *e2;
+ int i, j;
+ int c, c2;
+
+ c = 0;
+ c2 = 0;
+
+ for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ )
+ {
+ if( !e->inuse )
+ continue;
+
+ if( !e->team )
+ continue;
+
+ if( e->flags & FL_TEAMSLAVE )
+ continue;
+
+ e->teammaster = e;
+ c++;
+ c2++;
+
+ for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ )
+ {
+ if( !e2->inuse )
+ continue;
+
+ if( !e2->team )
+ continue;
+
+ if( e2->flags & FL_TEAMSLAVE )
+ continue;
+
+ if( !strcmp( e->team, e2->team ) )
+ {
+ c2++;
+ e2->teamchain = e->teamchain;
+ e->teamchain = e2;
+ e2->teammaster = e;
+ e2->flags |= FL_TEAMSLAVE;
+
+ // make sure that targets only point at the master
+ if( e2->targetname )
+ {
+ e->targetname = e2->targetname;
+ e2->targetname = NULL;
+ }
+ }
+ }
+ }
+
+ G_Printf( "%i teams with %i entities\n", c, c2 );
+}
+
+void G_RemapTeamShaders( void )
+{
+}
+
+
+/*
+=================
+G_RegisterCvars
+=================
+*/
+void G_RegisterCvars( void )
+{
+ int i;
+ cvarTable_t *cv;
+ qboolean remapped = qfalse;
+
+ for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )
+ {
+ trap_Cvar_Register( cv->vmCvar, cv->cvarName,
+ cv->defaultString, cv->cvarFlags );
+
+ if( cv->vmCvar )
+ cv->modificationCount = cv->vmCvar->modificationCount;
+
+ if( cv->teamShader )
+ remapped = qtrue;
+ }
+
+ if( remapped )
+ G_RemapTeamShaders( );
+}
+
+/*
+=================
+G_UpdateCvars
+=================
+*/
+void G_UpdateCvars( void )
+{
+ int i;
+ cvarTable_t *cv;
+ qboolean remapped = qfalse;
+
+ for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )
+ {
+ if( cv->vmCvar )
+ {
+ trap_Cvar_Update( cv->vmCvar );
+
+ if( cv->modificationCount != cv->vmCvar->modificationCount )
+ {
+ cv->modificationCount = cv->vmCvar->modificationCount;
+
+ if( cv->trackChange )
+ {
+ trap_SendServerCommand( -1, va( "print \"Server: %s changed to %s\n\"",
+ cv->cvarName, cv->vmCvar->string ) );
+ // update serverinfo in case this cvar is passed to clients indirectly
+ CalculateRanks( );
+ }
+
+ if( cv->teamShader )
+ remapped = qtrue;
+ }
+ }
+ }
+
+ if( remapped )
+ G_RemapTeamShaders( );
+}
+
+/*
+=================
+G_MapConfigs
+=================
+*/
+void G_MapConfigs( const char *mapname )
+{
+
+ if( !g_mapConfigs.string[0] )
+ return;
+
+ if( trap_Cvar_VariableIntegerValue( "g_mapConfigsLoaded" ) )
+ return;
+
+ trap_SendConsoleCommand( EXEC_APPEND,
+ va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) );
+
+ trap_SendConsoleCommand( EXEC_APPEND,
+ va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) );
+
+ trap_Cvar_Set( "g_mapConfigsLoaded", "1" );
+}
+
+/*
+============
+G_InitGame
+
+============
+*/
+void G_InitGame( int levelTime, int randomSeed, int restart )
+{
+ int i;
+
+ srand( randomSeed );
+
+ G_RegisterCvars( );
+
+ G_Printf( "------- Game Initialization -------\n" );
+ G_Printf( "gamename: %s\n", GAME_VERSION );
+ G_Printf( "gamedate: %s\n", __DATE__ );
+
+ G_ProcessIPBans( );
+
+ G_InitMemory( );
+
+ // set some level globals
+ memset( &level, 0, sizeof( level ) );
+ level.time = levelTime;
+ level.startTime = levelTime;
+ level.alienStage2Time = level.alienStage3Time =
+ level.humanStage2Time = level.humanStage3Time = level.startTime;
+
+ level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime
+
+ trap_Cvar_Set( "qvm_version",
+ QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")" );
+
+ if( g_logFile.string[ 0 ] )
+ {
+ if( g_logFileSync.integer )
+ trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND_SYNC );
+ else
+ trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND );
+
+ if( !level.logFile )
+ G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logFile.string );
+ else
+ {
+ char serverinfo[ MAX_INFO_STRING ];
+ qtime_t qt;
+ int t;
+
+
+ trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );
+
+ G_LogPrintf( "------------------------------------------------------------\n" );
+ G_LogPrintf( "InitGame: %s\n", serverinfo );
+
+ t = trap_RealTime( &qt );
+ G_LogPrintf("RealTime: %04i/%02i/%02i %02i:%02i:%02i\n",
+ qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday,
+ qt.tm_hour, qt.tm_min, qt.tm_sec );
+
+ }
+ }
+ else
+ G_Printf( "Not logging to disk\n" );
+
+ {
+ char map[ MAX_CVAR_VALUE_STRING ] = {""};
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ G_MapConfigs( map );
+ }
+
+ // we're done with g_mapConfigs, so reset this for the next map
+ trap_Cvar_Set( "g_mapConfigsLoaded", "0" );
+
+ if ( g_admin.string[ 0 ] ) {
+ G_admin_readconfig( NULL, 0 );
+ }
+
+ // initialize all entities for this game
+ memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) );
+ level.gentities = g_entities;
+
+ // initialize all clients for this game
+ level.maxclients = g_maxclients.integer;
+ memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[ 0 ] ) );
+ level.clients = g_clients;
+
+ // set client fields on player ents
+ for( i = 0; i < level.maxclients; i++ )
+ g_entities[ i ].client = level.clients + i;
+
+ // always leave room for the max number of clients,
+ // even if they aren't all used, so numbers inside that
+ // range are NEVER anything but clients
+ level.num_entities = MAX_CLIENTS;
+
+ // let the server system know where the entites are
+ trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
+ &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) );
+
+ trap_SetConfigstring( CS_INTERMISSION, "0" );
+
+ // update maplog
+ G_admin_maplog_update( );
+
+ // test to see if a custom buildable layout will be loaded
+ G_LayoutSelect( );
+
+ // parse the key/value pairs and spawn gentities
+ G_SpawnEntitiesFromString( );
+
+ // load up a custom building layout if there is one
+ G_LayoutLoad( );
+
+ // load any nobuild markers that have been saved
+ G_NobuildLoad( );
+
+ // the map might disable some things
+ BG_InitAllowedGameElements( );
+
+ // general initialization
+ G_FindTeams( );
+
+ //TA:
+ BG_InitClassOverrides( );
+ BG_InitBuildableOverrides( );
+ G_InitDamageLocations( );
+ G_InitMapRotations( );
+ G_InitSpawnQueue( &level.alienSpawnQueue );
+ G_InitSpawnQueue( &level.humanSpawnQueue );
+
+ if( g_debugMapRotation.integer )
+ G_PrintRotations( );
+
+ //reset stages
+ trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) );
+ trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) );
+ trap_Cvar_Set( "g_alienKills", 0 );
+ trap_Cvar_Set( "g_humanKills", 0 );
+ trap_Cvar_Set( "g_suddenDeath", 0 );
+ level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;
+
+ G_Printf( "-----------------------------------\n" );
+
+ G_RemapTeamShaders( );
+
+ //TA: so the server counts the spawns without a client attached
+ G_CountSpawns( );
+
+ G_ResetPTRConnections( );
+
+ if(g_lockTeamsAtStart.integer)
+ {
+ level.alienTeamLocked=qtrue;
+ level.humanTeamLocked=qtrue;
+ trap_Cvar_Set( "g_lockTeamsAtStart", "0" );
+ }
+}
+
+/*
+==================
+G_ClearVotes
+
+remove all currently active votes
+==================
+*/
+static void G_ClearVotes( void )
+{
+ level.voteTime = 0;
+ trap_SetConfigstring( CS_VOTE_TIME, "" );
+ trap_SetConfigstring( CS_VOTE_STRING, "" );
+ level.teamVoteTime[ 0 ] = 0;
+ trap_SetConfigstring( CS_TEAMVOTE_TIME, "" );
+ trap_SetConfigstring( CS_TEAMVOTE_STRING, "" );
+ level.teamVoteTime[ 1 ] = 0;
+ trap_SetConfigstring( CS_TEAMVOTE_TIME + 1, "" );
+ trap_SetConfigstring( CS_TEAMVOTE_STRING + 1, "" );
+}
+
+/*
+=================
+G_ShutdownGame
+=================
+*/
+void G_ShutdownGame( int restart )
+{
+ // in case of a map_restart
+ G_ClearVotes( );
+
+ G_Printf( "==== ShutdownGame ====\n" );
+
+ if( level.logFile )
+ {
+ G_LogPrintf( "ShutdownGame:\n" );
+ G_LogPrintf( "------------------------------------------------------------\n" );
+ trap_FS_FCloseFile( level.logFile );
+ }
+
+ // write admin.dat for !seen data
+ admin_writeconfig();
+
+ // write all the client session data so we can get it back
+ G_WriteSessionData( );
+
+ G_admin_cleanup( );
+ G_admin_namelog_cleanup( );
+ G_admin_adminlog_cleanup( );
+
+ level.restarted = qfalse;
+ level.surrenderTeam = PTE_NONE;
+ trap_SetConfigstring( CS_WINNER, "" );
+}
+
+
+
+//===================================================================
+
+void QDECL Com_Error( int level, const char *error, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, error );
+ vsprintf( text, error, argptr );
+ va_end( argptr );
+
+ G_Error( "%s", text );
+}
+
+void QDECL Com_Printf( const char *msg, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, msg );
+ vsprintf( text, msg, argptr );
+ va_end( argptr );
+
+ G_Printf( "%s", text );
+}
+
+/*
+========================================================================
+
+PLAYER COUNTING / SCORE SORTING
+
+========================================================================
+*/
+
+
+/*
+=============
+SortRanks
+
+=============
+*/
+int QDECL SortRanks( const void *a, const void *b )
+{
+ gclient_t *ca, *cb;
+
+ ca = &level.clients[ *(int *)a ];
+ cb = &level.clients[ *(int *)b ];
+
+ // then sort by score
+ if( ca->pers.score > cb->pers.score )
+ return -1;
+ else if( ca->pers.score < cb->pers.score )
+ return 1;
+ else
+ return 0;
+}
+
+/*
+============
+G_InitSpawnQueue
+
+Initialise a spawn queue
+============
+*/
+void G_InitSpawnQueue( spawnQueue_t *sq )
+{
+ int i;
+
+ sq->back = sq->front = 0;
+ sq->back = QUEUE_MINUS1( sq->back );
+
+ //0 is a valid clientNum, so use something else
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ sq->clients[ i ] = -1;
+}
+
+/*
+============
+G_GetSpawnQueueLength
+
+Return tha length of a spawn queue
+============
+*/
+int G_GetSpawnQueueLength( spawnQueue_t *sq )
+{
+ int length = sq->back - sq->front + 1;
+
+ while( length < 0 )
+ length += MAX_CLIENTS;
+
+ while( length >= MAX_CLIENTS )
+ length -= MAX_CLIENTS;
+
+ return length;
+}
+
+/*
+============
+G_PopSpawnQueue
+
+Remove from front element from a spawn queue
+============
+*/
+int G_PopSpawnQueue( spawnQueue_t *sq )
+{
+ int clientNum = sq->clients[ sq->front ];
+
+ if( G_GetSpawnQueueLength( sq ) > 0 )
+ {
+ sq->clients[ sq->front ] = -1;
+ sq->front = QUEUE_PLUS1( sq->front );
+ g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED;
+
+ return clientNum;
+ }
+ else
+ return -1;
+}
+
+/*
+============
+G_PeekSpawnQueue
+
+Look at front element from a spawn queue
+============
+*/
+int G_PeekSpawnQueue( spawnQueue_t *sq )
+{
+ return sq->clients[ sq->front ];
+}
+
+/*
+============
+G_SearchSpawnQueue
+
+Look to see if clientNum is already in the spawnQueue
+============
+*/
+qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum )
+{
+ int i;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ if( sq->clients[ i ] == clientNum )
+ return qtrue;
+ return qfalse;
+}
+
+/*
+============
+G_PushSpawnQueue
+
+Add an element to the back of the spawn queue
+============
+*/
+qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum )
+{
+ // don't add the same client more than once
+ if( G_SearchSpawnQueue( sq, clientNum ) )
+ return qfalse;
+
+ sq->back = QUEUE_PLUS1( sq->back );
+ sq->clients[ sq->back ] = clientNum;
+
+ g_entities[ clientNum ].client->ps.pm_flags |= PMF_QUEUED;
+ return qtrue;
+}
+
+/*
+============
+G_RemoveFromSpawnQueue
+
+remove a specific client from a spawn queue
+============
+*/
+qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum )
+{
+ int i = sq->front;
+
+ if( G_GetSpawnQueueLength( sq ) )
+ {
+ do
+ {
+ if( sq->clients[ i ] == clientNum )
+ {
+ //and this kids is why it would have
+ //been better to use an LL for internal
+ //representation
+ do
+ {
+ sq->clients[ i ] = sq->clients[ QUEUE_PLUS1( i ) ];
+
+ i = QUEUE_PLUS1( i );
+ } while( i != QUEUE_PLUS1( sq->back ) );
+
+ sq->back = QUEUE_MINUS1( sq->back );
+ g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED;
+
+ return qtrue;
+ }
+
+ i = QUEUE_PLUS1( i );
+ } while( i != QUEUE_PLUS1( sq->back ) );
+ }
+
+ return qfalse;
+}
+
+/*
+============
+G_GetPosInSpawnQueue
+
+Get the position of a client in a spawn queue
+============
+*/
+int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum )
+{
+ int i = sq->front;
+
+ if( G_GetSpawnQueueLength( sq ) )
+ {
+ do
+ {
+ if( sq->clients[ i ] == clientNum )
+ {
+ if( i < sq->front )
+ return i + MAX_CLIENTS - sq->front;
+ else
+ return i - sq->front;
+ }
+
+ i = QUEUE_PLUS1( i );
+ } while( i != QUEUE_PLUS1( sq->back ) );
+ }
+
+ return -1;
+}
+
+/*
+============
+G_PrintSpawnQueue
+
+Print the contents of a spawn queue
+============
+*/
+void G_PrintSpawnQueue( spawnQueue_t *sq )
+{
+ int i = sq->front;
+ int length = G_GetSpawnQueueLength( sq );
+
+ G_Printf( "l:%d f:%d b:%d :", length, sq->front, sq->back );
+
+ if( length > 0 )
+ {
+ do
+ {
+ if( sq->clients[ i ] == -1 )
+ G_Printf( "*:" );
+ else
+ G_Printf( "%d:", sq->clients[ i ] );
+
+ i = QUEUE_PLUS1( i );
+ } while( i != QUEUE_PLUS1( sq->back ) );
+ }
+
+ G_Printf( "\n" );
+}
+
+/*
+============
+G_SpawnClients
+
+Spawn queued clients
+============
+*/
+void G_SpawnClients( pTeam_t team )
+{
+ int clientNum;
+ gentity_t *ent, *spawn;
+ vec3_t spawn_origin, spawn_angles;
+ spawnQueue_t *sq = NULL;
+ int numSpawns = 0;
+ if( g_doWarmup.integer && ( g_warmupMode.integer==1 || g_warmupMode.integer == 2 ) &&
+ level.time - level.startTime < g_warmup.integer * 1000 )
+ {
+ return;
+ }
+ if( team == PTE_ALIENS )
+ {
+ sq = &level.alienSpawnQueue;
+ numSpawns = level.numAlienSpawns;
+ }
+ else if( team == PTE_HUMANS )
+ {
+ sq = &level.humanSpawnQueue;
+ numSpawns = level.numHumanSpawns;
+ }
+
+ if( G_GetSpawnQueueLength( sq ) > 0 && numSpawns > 0 )
+ {
+ clientNum = G_PeekSpawnQueue( sq );
+ ent = &g_entities[ clientNum ];
+
+ if( ( spawn = G_SelectTremulousSpawnPoint( team,
+ ent->client->pers.lastDeathLocation,
+ spawn_origin, spawn_angles ) ) )
+ {
+ clientNum = G_PopSpawnQueue( sq );
+
+ if( clientNum < 0 )
+ return;
+
+ ent = &g_entities[ clientNum ];
+
+ ent->client->sess.sessionTeam = TEAM_FREE;
+ ClientUserinfoChanged( clientNum, qfalse );
+ ClientSpawn( ent, spawn, spawn_origin, spawn_angles );
+ }
+ }
+}
+
+/*
+============
+G_CountSpawns
+
+Counts the number of spawns for each team
+============
+*/
+void G_CountSpawns( void )
+{
+ int i;
+ gentity_t *ent;
+
+ level.numAlienSpawns = 0;
+ level.numHumanSpawns = 0;
+
+ for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( ent->s.modelindex == BA_A_SPAWN && ent->health > 0 )
+ level.numAlienSpawns++;
+
+ if( ent->s.modelindex == BA_H_SPAWN && ent->health > 0 )
+ level.numHumanSpawns++;
+ }
+
+ //let the client know how many spawns there are
+ trap_SetConfigstring( CS_SPAWNS, va( "%d %d",
+ level.numAlienSpawns, level.numHumanSpawns ) );
+}
+
+/*
+============
+G_TimeTilSuddenDeath
+============
+*/
+int G_TimeTilSuddenDeath( void )
+{
+ if( (!g_suddenDeathTime.integer && level.suddenDeathBeginTime==0 ) || level.suddenDeathBeginTime<0 )
+ return 999999999; // Always some time away
+
+ return ( ( level.suddenDeathBeginTime ) - ( level.time - level.startTime ) );
+}
+
+
+#define PLAYER_COUNT_MOD 5.0f
+
+/*
+============
+G_CalculateBuildPoints
+
+Recalculate the quantity of building points available to the teams
+============
+*/
+void G_CalculateBuildPoints( void )
+{
+ int i;
+ buildable_t buildable;
+ gentity_t *ent;
+ int localHTP = g_humanBuildPoints.integer,
+ localATP = g_alienBuildPoints.integer;
+
+ // g_suddenDeath sets what state we want it to be.
+ // level.suddenDeath says whether we've calculated BPs at the 'start' of SD or not
+
+ // reset if SD was on, but now it's off
+ if(!g_suddenDeath.integer && level.suddenDeath)
+ {
+ level.suddenDeath = qfalse;
+ level.suddenDeathWarning = 0;
+ level.suddenDeathBeginTime = -1;
+ if((level.time - level.startTime) < (g_suddenDeathTime.integer * 60000 ) )
+ level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;
+ else
+ level.suddenDeathBeginTime = -1;
+ }
+
+ if(!level.suddenDeath)
+ {
+ if(g_suddenDeath.integer || G_TimeTilSuddenDeath( ) <= 0 ) //Conditions to enter SD
+ {
+ //begin sudden death
+ if( level.suddenDeathWarning < TW_PASSED )
+ {
+ trap_SendServerCommand( -1, "cp \"Sudden Death!\"" );
+ G_LogPrintf("Beginning Sudden Death (Mode %d)\n",g_suddenDeathMode.integer);
+ localHTP = 0;
+ localATP = 0;
+
+ if( g_suddenDeathMode.integer == SDMODE_SELECTIVE )
+ {
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( BG_FindReplaceableTestForBuildable( ent->s.modelindex ) )
+ {
+ int t = BG_FindTeamForBuildable( ent->s.modelindex );
+
+ if( t == BIT_HUMANS )
+ localHTP += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ else if( t == BIT_ALIENS )
+ localATP += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ }
+ }
+ }
+ level.suddenDeathHBuildPoints = localHTP;
+ level.suddenDeathABuildPoints = localATP;
+ level.suddenDeathBeginTime = level.time;
+ level.suddenDeath=qtrue;
+ trap_Cvar_Set( "g_suddenDeath", "1" );
+
+ level.suddenDeathWarning = TW_PASSED;
+ }
+ }
+ else
+ {
+ //warn about sudden death
+ if( ( G_TimeTilSuddenDeath( ) <= 60000 ) &&
+ ( level.suddenDeathWarning < TW_IMMINENT ) )
+ {
+ trap_SendServerCommand( -1, va("cp \"Sudden Death in %d seconds!\"",
+ (int)(G_TimeTilSuddenDeath() / 1000 ) ) );
+ level.suddenDeathWarning = TW_IMMINENT;
+ }
+ }
+ }
+
+ //set BP at each cycle
+ if( g_suddenDeath.integer )
+ {
+ localHTP = level.suddenDeathHBuildPoints;
+ localATP = level.suddenDeathABuildPoints;
+ }
+ else
+ {
+ localHTP = g_humanBuildPoints.integer;
+ localATP = g_alienBuildPoints.integer;
+ }
+
+ level.humanBuildPoints = level.humanBuildPointsPowered = localHTP;
+ level.alienBuildPoints = localATP;
+
+ level.reactorPresent = qfalse;
+ level.overmindPresent = qfalse;
+
+ for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ buildable = ent->s.modelindex;
+
+ if( buildable != BA_NONE )
+ {
+ if( buildable == BA_H_REACTOR && ent->spawned && ent->health > 0 )
+ level.reactorPresent = qtrue;
+
+ if( buildable == BA_A_OVERMIND && ent->spawned && ent->health > 0 )
+ level.overmindPresent = qtrue;
+
+ if( !g_suddenDeath.integer || BG_FindReplaceableTestForBuildable( buildable ) )
+ {
+ if( BG_FindTeamForBuildable( buildable ) == BIT_HUMANS )
+ {
+ level.humanBuildPoints -= BG_FindBuildPointsForBuildable( buildable );
+ if( ent->powered )
+ level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable( buildable );
+ }
+ else
+ {
+ level.alienBuildPoints -= BG_FindBuildPointsForBuildable( buildable );
+ }
+ }
+ }
+ }
+
+ if( level.humanBuildPoints < 0 )
+ {
+ localHTP -= level.humanBuildPoints;
+ level.humanBuildPointsPowered -= level.humanBuildPoints;
+ level.humanBuildPoints = 0;
+ }
+
+ if( level.alienBuildPoints < 0 )
+ {
+ localATP -= level.alienBuildPoints;
+ level.alienBuildPoints = 0;
+ }
+
+ trap_SetConfigstring( CS_BUILDPOINTS, va( "%d %d %d %d %d",
+ level.alienBuildPoints, localATP,
+ level.humanBuildPoints, localHTP,
+ level.humanBuildPointsPowered ) );
+
+ //may as well pump the stages here too
+ {
+ float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
+ float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
+ int alienNextStageThreshold, humanNextStageThreshold;
+
+ if( alienPlayerCountMod < 0.1f )
+ alienPlayerCountMod = 0.1f;
+
+ if( humanPlayerCountMod < 0.1f )
+ humanPlayerCountMod = 0.1f;
+
+ if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 )
+ alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) );
+ else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 )
+ alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) );
+ else
+ alienNextStageThreshold = -1;
+
+ if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 )
+ humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) );
+ else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 )
+ humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) );
+ else
+ humanNextStageThreshold = -1;
+
+ trap_SetConfigstring( CS_STAGES, va( "%d %d %d %d %d %d",
+ g_alienStage.integer, g_humanStage.integer,
+ g_alienKills.integer, g_humanKills.integer,
+ alienNextStageThreshold, humanNextStageThreshold ) );
+ }
+}
+
+/*
+============
+G_CalculateStages
+============
+*/
+void G_CalculateStages( void )
+{
+ float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
+ float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
+ static int lastAlienStageModCount = 1;
+ static int lastHumanStageModCount = 1;
+
+ if( alienPlayerCountMod < 0.1f )
+ alienPlayerCountMod = 0.1f;
+
+ if( humanPlayerCountMod < 0.1f )
+ humanPlayerCountMod = 0.1f;
+
+ if( g_alienKills.integer >=
+ (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) &&
+ g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 )
+ {
+ trap_Cvar_Set( "g_alienStage", va( "%d", S2 ) );
+ level.alienStage2Time = level.time;
+ lastAlienStageModCount = g_alienStage.modificationCount;
+ G_LogPrintf("Stage: A 2: Aliens reached Stage 2\n");
+ }
+
+ if( g_alienKills.integer >=
+ (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) &&
+ g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 )
+ {
+ trap_Cvar_Set( "g_alienStage", va( "%d", S3 ) );
+ level.alienStage3Time = level.time;
+ lastAlienStageModCount = g_alienStage.modificationCount;
+ G_LogPrintf("Stage: A 3: Aliens reached Stage 3\n");
+ }
+
+ if( g_humanKills.integer >=
+ (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) &&
+ g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 )
+ {
+ trap_Cvar_Set( "g_humanStage", va( "%d", S2 ) );
+ level.humanStage2Time = level.time;
+ lastHumanStageModCount = g_humanStage.modificationCount;
+ G_LogPrintf("Stage: H 2: Humans reached Stage 2\n");
+ }
+
+ if( g_humanKills.integer >=
+ (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) &&
+ g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 )
+ {
+ trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) );
+ level.humanStage3Time = level.time;
+ G_LogPrintf("Stage: H 3: Humans reached Stage 3\n");
+ lastHumanStageModCount = g_humanStage.modificationCount;
+ }
+
+ if( g_alienStage.modificationCount > lastAlienStageModCount )
+ {
+ G_Checktrigger_stages( PTE_ALIENS, g_alienStage.integer );
+ if( g_alienStage.integer == S2 )
+ level.alienStage2Time = level.time;
+ else if( g_alienStage.integer == S3 )
+ level.alienStage3Time = level.time;
+
+ lastAlienStageModCount = g_alienStage.modificationCount;
+ }
+
+ if( g_humanStage.modificationCount > lastHumanStageModCount )
+ {
+ G_Checktrigger_stages( PTE_HUMANS, g_humanStage.integer );
+
+ if( g_humanStage.integer == S2 )
+ level.humanStage2Time = level.time;
+ else if( g_humanStage.integer == S3 )
+ level.humanStage3Time = level.time;
+
+ lastHumanStageModCount = g_humanStage.modificationCount;
+ }
+}
+
+/*
+============
+CalculateAvgPlayers
+
+Calculates the average number of players playing this game
+============
+*/
+void G_CalculateAvgPlayers( void )
+{
+ //there are no clients or only spectators connected, so
+ //reset the number of samples in order to avoid the situation
+ //where the average tends to 0
+ if( !level.numAlienClients )
+ {
+ level.numAlienSamples = 0;
+ trap_Cvar_Set( "g_alienKills", "0" );
+ }
+
+ if( !level.numHumanClients )
+ {
+ level.numHumanSamples = 0;
+ trap_Cvar_Set( "g_humanKills", "0" );
+ }
+
+ //calculate average number of clients for stats
+ level.averageNumAlienClients =
+ ( ( level.averageNumAlienClients * level.numAlienSamples )
+ + level.numAlienClients ) /
+ (float)( level.numAlienSamples + 1 );
+ level.numAlienSamples++;
+
+ level.averageNumHumanClients =
+ ( ( level.averageNumHumanClients * level.numHumanSamples )
+ + level.numHumanClients ) /
+ (float)( level.numHumanSamples + 1 );
+ level.numHumanSamples++;
+}
+
+/*
+============
+CalculateRanks
+
+Recalculates the score ranks of all players
+This will be called on every client connect, begin, disconnect, death,
+and team change.
+============
+*/
+void CalculateRanks( void )
+{
+ int i;
+ char P[ MAX_CLIENTS + 1 ] = {""};
+ int ff = 0;
+
+ level.numConnectedClients = 0;
+ level.numNonSpectatorClients = 0;
+ level.numPlayingClients = 0;
+ level.numVotingClients = 0; // don't count bots
+ level.numAlienClients = 0;
+ level.numHumanClients = 0;
+ level.numLiveAlienClients = 0;
+ level.numLiveHumanClients = 0;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ P[ i ] = '-';
+ if ( level.clients[ i ].pers.connected != CON_DISCONNECTED )
+ {
+ level.sortedClients[ level.numConnectedClients ] = i;
+ level.numConnectedClients++;
+ P[ i ] = (char)'0' + level.clients[ i ].pers.teamSelection;
+
+ if( level.clients[ i ].pers.connected != CON_CONNECTED )
+ continue;
+
+ level.numVotingClients++;
+ if( level.clients[ i ].pers.teamSelection != PTE_NONE )
+ {
+ level.numPlayingClients++;
+ if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
+ level.numNonSpectatorClients++;
+
+ if( level.clients[ i ].pers.teamSelection == PTE_ALIENS )
+ {
+ level.numAlienClients++;
+ if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
+ level.numLiveAlienClients++;
+ }
+ else if( level.clients[ i ].pers.teamSelection == PTE_HUMANS )
+ {
+ level.numHumanClients++;
+ if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
+ level.numLiveHumanClients++;
+ }
+ }
+ }
+ }
+ level.numteamVotingClients[ 0 ] = level.numHumanClients;
+ level.numteamVotingClients[ 1 ] = level.numAlienClients;
+ P[ i ] = '\0';
+ trap_Cvar_Set( "P", P );
+
+ if( g_friendlyFire.value>0 )
+ ff |= ( FFF_HUMANS | FFF_ALIENS );
+ if( g_friendlyFireHumans.value>0 )
+ ff |= FFF_HUMANS;
+ if( g_friendlyFireAliens.value>0 )
+ ff |= FFF_ALIENS;
+ if( g_friendlyBuildableFire.value>0 )
+ ff |= FFF_BUILDABLES;
+ trap_Cvar_Set( "ff", va( "%i", ff ) );
+
+ qsort( level.sortedClients, level.numConnectedClients,
+ sizeof( level.sortedClients[ 0 ] ), SortRanks );
+
+ // see if it is time to end the level
+ CheckExitRules( );
+
+ // if we are at the intermission, send the new info to everyone
+ if( level.intermissiontime && !level.mapRotationVoteTime )
+ SendScoreboardMessageToAllClients( );
+}
+
+
+/*
+========================================================================
+
+MAP CHANGING
+
+========================================================================
+*/
+
+/*
+========================
+SendScoreboardMessageToAllClients
+
+Do this at BeginIntermission time and whenever ranks are recalculated
+due to enters/exits/forced team changes
+========================
+*/
+void SendScoreboardMessageToAllClients( void )
+{
+ int i;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_CONNECTED )
+ ScoreboardMessage( g_entities + i );
+ }
+}
+
+/*
+========================
+MoveClientToIntermission
+
+When the intermission starts, this will be called for all players.
+If a new client connects, this will be called after the spawn function.
+========================
+*/
+void MoveClientToIntermission( gentity_t *ent )
+{
+ // take out of follow mode if needed
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+
+ // move to the spot
+ VectorCopy( level.intermission_origin, ent->s.origin );
+ VectorCopy( level.intermission_origin, ent->client->ps.origin );
+ VectorCopy( level.intermission_angle, ent->client->ps.viewangles );
+ ent->client->ps.pm_type = PM_INTERMISSION;
+
+ // clean up powerup info
+ memset( ent->client->ps.misc, 0, sizeof( ent->client->ps.misc ) );
+
+ ent->client->ps.eFlags = 0;
+ ent->s.eFlags = 0;
+ ent->s.eType = ET_GENERAL;
+ ent->s.modelindex = 0;
+ ent->s.loopSound = 0;
+ ent->s.event = 0;
+ ent->r.contents = 0;
+}
+
+/*
+==================
+FindIntermissionPoint
+
+This is also used for spectator spawns
+==================
+*/
+void FindIntermissionPoint( void )
+{
+ gentity_t *ent, *target;
+ vec3_t dir;
+
+ // find the intermission spot
+ ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" );
+
+ if( !ent )
+ { // the map creator forgot to put in an intermission point...
+ G_SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle );
+ }
+ else
+ {
+ VectorCopy( ent->s.origin, level.intermission_origin );
+ VectorCopy( ent->s.angles, level.intermission_angle );
+ // if it has a target, look towards it
+ if( ent->target )
+ {
+ target = G_PickTarget( ent->target );
+
+ if( target )
+ {
+ VectorSubtract( target->s.origin, level.intermission_origin, dir );
+ vectoangles( dir, level.intermission_angle );
+ }
+ }
+ }
+
+}
+
+/*
+==================
+BeginIntermission
+==================
+*/
+void BeginIntermission( void )
+{
+ int i;
+ gentity_t *client;
+
+ if( level.intermissiontime )
+ return; // already active
+
+ level.numTeamWarnings = 99;
+
+ level.intermissiontime = level.time;
+
+ G_ClearVotes( );
+
+ FindIntermissionPoint( );
+
+ // move all clients to the intermission point
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ client = g_entities + i;
+
+ if( !client->inuse )
+ continue;
+
+ // respawn if dead
+ if( client->health <= 0 )
+ respawn(client);
+
+ MoveClientToIntermission( client );
+ }
+
+ // send the current scoring to all clients
+ SendScoreboardMessageToAllClients( );
+
+ if( g_nextMap.string[ 0 ] )
+ {
+ trap_SendServerCommand( -1,
+ va( "print \"next map has been set to %s^7%s\n\"",
+ g_nextMap.string,
+ ( G_CheckMapRotationVote() ) ? ", voting will be skipped" : "" ) );
+ }
+}
+
+void BeginMapRotationVote( void )
+{
+ gentity_t *ent;
+ int length;
+ int i;
+
+ if( level.mapRotationVoteTime )
+ return;
+
+ length = g_mapRotationVote.integer;
+ if( length > 60 )
+ length = 60;
+ level.mapRotationVoteTime = level.time + ( length * 1000 );
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ ent = g_entities + i;
+
+ if( !ent->inuse )
+ continue;
+
+ ent->client->ps.pm_type = PM_SPECTATOR;
+ ent->client->sess.sessionTeam = TEAM_SPECTATOR;
+ ent->client->sess.spectatorState = SPECTATOR_LOCKED;
+ }
+}
+
+/*
+=============
+ExitLevel
+
+When the intermission has been exited, the server is either moved
+to a new map based on the map rotation or the current map restarted
+=============
+*/
+void ExitLevel( void )
+{
+ int i;
+ gclient_t *cl;
+ buildHistory_t *tmp, *mark;
+
+ if( level.mapRotationVoteTime )
+ {
+ if( level.time < level.mapRotationVoteTime &&
+ !G_IntermissionMapVoteWinner( ) )
+ return;
+ }
+ else if( g_mapRotationVote.integer > 0 &&
+ G_CheckMapRotationVote() &&
+ !g_nextMap.string[ 0 ] )
+ {
+ BeginMapRotationVote( );
+ return;
+ }
+
+ while( ( tmp = level.buildHistory ) )
+ {
+ level.buildHistory = level.buildHistory->next;
+ while( ( mark = tmp ) )
+ {
+ tmp = tmp->marked;
+ G_Free( mark );
+ }
+ }
+
+ if ( G_MapExists( g_nextMap.string ) )
+ trap_SendConsoleCommand( EXEC_APPEND, va("!map %s\n", g_nextMap.string ) );
+ else if( G_MapRotationActive( ) )
+ G_AdvanceMapRotation( );
+ else
+ trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" );
+
+ trap_Cvar_Set( "g_nextMap", "" );
+
+ level.restarted = qtrue;
+ level.changemap = NULL;
+ level.intermissiontime = 0;
+
+ // reset all the scores so we don't enter the intermission again
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ cl->ps.persistant[ PERS_SCORE ] = 0;
+ }
+
+ // we need to do this here before chaning to CON_CONNECTING
+ G_WriteSessionData( );
+
+ // change all client states to connecting, so the early players into the
+ // next level will know the others aren't done reconnecting
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_CONNECTED )
+ level.clients[ i ].pers.connected = CON_CONNECTING;
+ }
+
+}
+/*
+=================
+G_AdminsPrintf
+
+Print to all active admins, and the logfile with a time stamp if it is open, and to the console
+=================
+*/
+void QDECL G_AdminsPrintf( const char *fmt, ... )
+{
+ va_list argptr;
+ char string[ 1024 ];
+ gentity_t *tempent;
+ int j;
+
+ va_start( argptr, fmt );
+ vsprintf( string, fmt,argptr );
+ va_end( argptr );
+
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ tempent = &g_entities[ j ];
+ if( G_admin_permission( tempent, ADMF_ADMINCHAT ) &&
+ !tempent->client->pers.ignoreAdminWarnings )
+ {
+ trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Admins]^7 %s\"", string) );
+ }
+ }
+
+ G_LogPrintf("%s",string);
+
+}
+/*
+=================
+G_WarningsPrintf
+
+Print to everyone with a certain flag, and the logfile with a time stamp if it is open, and to the console
+(just a copy of the G_AdminsPrintf with flag suport)
+=================
+*/
+void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... )
+{
+ va_list argptr;
+ char string[ 1024 ];
+ gentity_t *tempent;
+ int j;
+
+ va_start( argptr, fmt );
+ vsprintf( string, fmt,argptr );
+ va_end( argptr );
+
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ tempent = &g_entities[ j ];
+ if( G_admin_permission( tempent, flag ) )
+ {
+ trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Warnings]^7 %s\"", string) );
+ }
+ }
+
+ G_LogPrintf("%s",string);
+
+}
+/*
+=================
+G_LogPrintf
+
+Print to the logfile with a time stamp if it is open
+=================
+*/
+void QDECL G_LogPrintf( const char *fmt, ... )
+{
+ va_list argptr;
+ char string[ 1024 ], decoloured[ 1024 ];
+ int min, tens, sec;
+
+ sec = ( level.time - level.startTime ) / 1000;
+
+ min = sec / 60;
+ sec -= min * 60;
+ tens = sec / 10;
+ sec -= tens * 10;
+
+ Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
+
+ va_start( argptr, fmt );
+ vsprintf( string +7 , fmt,argptr );
+ va_end( argptr );
+
+ if( g_dedicated.integer )
+ G_Printf( "%s", string + 7 );
+
+ if( !level.logFile )
+ return;
+
+ if( g_decolourLogfiles.integer )
+ {
+ G_DecolorString( string, decoloured );
+ trap_FS_Write( decoloured, strlen( decoloured ), level.logFile );
+ }
+ else
+ {
+ trap_FS_Write( string, strlen( string ), level.logFile );
+ }
+}
+
+/*
+=================
+G_LogPrintfColoured
+
+Bypasses g_decolourLogfiles for events that need colors in the logs
+=================
+*/
+void QDECL G_LogPrintfColoured( const char *fmt, ... )
+{
+ va_list argptr;
+ char string[ 1024 ];
+ int min, tens, sec;
+
+ sec = (level.time - level.startTime) / 1000;
+
+ min = sec / 60;
+ sec -= min * 60;
+ tens = sec / 10;
+ sec -= tens * 10;
+
+ Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
+
+ va_start( argptr, fmt );
+ vsprintf( string +7 , fmt,argptr );
+ va_end( argptr );
+
+ if( g_dedicated.integer )
+ G_Printf( "%s", string + 7 );
+
+ if( !level.logFile )
+ return;
+
+ trap_FS_Write( string, strlen( string ), level.logFile );
+}
+
+/*
+=================
+G_LogOnlyPrintf
+
+Print to the logfile only (not console) with a time stamp if it is open
+=================
+*/
+void QDECL G_LogOnlyPrintf( const char *fmt, ... )
+{
+ va_list argptr;
+ char string[ 1024 ], decoloured[ 1024 ];
+ int min, tens, sec;
+
+ sec = (level.time - level.startTime) / 1000;
+
+ min = sec / 60;
+ sec -= min * 60;
+ tens = sec / 10;
+ sec -= tens * 10;
+
+ Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
+
+ va_start( argptr, fmt );
+ vsprintf( string +7 , fmt,argptr );
+ va_end( argptr );
+
+ if( !level.logFile )
+ return;
+
+ if( g_decolourLogfiles.integer )
+ {
+ G_DecolorString( string, decoloured );
+ trap_FS_Write( decoloured, strlen( decoloured ), level.logFile );
+ }
+ else
+ {
+ trap_FS_Write( string, strlen( string ), level.logFile );
+ }
+}
+
+/*
+=================
+G_SendGameStat
+=================
+*/
+void G_SendGameStat( pTeam_t team )
+{
+ char map[ MAX_STRING_CHARS ];
+ char teamChar;
+ char data[ BIG_INFO_STRING ];
+ char entry[ MAX_STRING_CHARS ];
+ int i, dataLength, entryLength;
+ gclient_t *cl;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ switch( team )
+ {
+ case PTE_ALIENS: teamChar = 'A'; break;
+ case PTE_HUMANS: teamChar = 'H'; break;
+ case PTE_NONE: teamChar = 'L'; break;
+ default: return;
+ }
+
+ Com_sprintf( data, BIG_INFO_STRING,
+ "%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d",
+ Q3_VERSION,
+ g_tag.string,
+ teamChar,
+ level.averageNumAlienClients,
+ level.averageNumHumanClients,
+ map,
+ level.time - level.startTime,
+ G_TimeTilSuddenDeath( ),
+ g_alienStage.integer,
+ level.alienStage2Time - level.startTime,
+ level.alienStage3Time - level.startTime,
+ g_humanStage.integer,
+ level.humanStage2Time - level.startTime,
+ level.humanStage3Time - level.startTime,
+ level.numConnectedClients );
+
+ dataLength = strlen( data );
+
+ for( i = 0; i < level.numConnectedClients; i++ )
+ {
+ int ping;
+
+ cl = &level.clients[ level.sortedClients[ i ] ];
+
+ // Ignore invisible players
+ if ( cl->sess.invisible == qtrue )
+ continue;
+
+ if( cl->pers.connected == CON_CONNECTING )
+ ping = -1;
+ else
+ ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
+
+ switch( cl->ps.stats[ STAT_PTEAM ] )
+ {
+ case PTE_ALIENS: teamChar = 'A'; break;
+ case PTE_HUMANS: teamChar = 'H'; break;
+ case PTE_NONE: teamChar = 'S'; break;
+ default: return;
+ }
+
+ Com_sprintf( entry, MAX_STRING_CHARS,
+ " \"%s\" %c %d %d %d",
+ cl->pers.netname,
+ teamChar,
+ cl->ps.persistant[ PERS_SCORE ],
+ ping,
+ ( level.time - cl->pers.enterTime ) / 60000 );
+
+ entryLength = strlen( entry );
+
+ if( dataLength + entryLength >= BIG_INFO_STRING )
+ break;
+
+ strcpy( data + dataLength, entry );
+ dataLength += entryLength;
+ }
+
+ trap_SendGameStat( data );
+}
+
+/*
+================
+LogExit
+
+Append information about this game to the log file
+================
+*/
+void LogExit( const char *string )
+{
+ int i, numSorted;
+ gclient_t *cl;
+ gentity_t *ent;
+
+ G_LogPrintf( "Exit: %s\n", string );
+
+ level.intermissionQueued = level.time;
+
+ // this will keep the clients from playing any voice sounds
+ // that will get cut off when the queued intermission starts
+ trap_SetConfigstring( CS_INTERMISSION, "1" );
+
+ // don't send more than 32 scores (FIXME?)
+ numSorted = level.numConnectedClients;
+ if( numSorted > 32 )
+ numSorted = 32;
+
+ for( i = 0; i < numSorted; i++ )
+ {
+ int ping;
+
+ cl = &level.clients[ level.sortedClients[ i ] ];
+
+ if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE )
+ continue;
+
+ if( cl->pers.connected == CON_CONNECTING )
+ continue;
+
+ ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
+
+ G_LogPrintf( "score: %i ping: %i client: %i %s\n",
+ cl->ps.persistant[ PERS_SCORE ], ping, level.sortedClients[ i ],
+ cl->pers.netname );
+
+ }
+
+ for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( !Q_stricmp( ent->classname, "trigger_win" ) )
+ {
+ if( level.lastWin == ent->stageTeam )
+ ent->use( ent, ent, ent );
+ }
+ }
+
+ G_SendGameStat( level.lastWin );
+}
+
+
+/*
+=================
+CheckIntermissionExit
+
+The level will stay at the intermission for a minimum of 5 seconds
+If all players wish to continue, the level will then exit.
+If one or more players have not acknowledged the continue, the game will
+wait 10 seconds before going on.
+=================
+*/
+void CheckIntermissionExit( void )
+{
+ int ready, notReady, numPlayers;
+ int i;
+ gclient_t *cl;
+ int readyMask;
+
+ //if no clients are connected, just exit
+ if( !level.numConnectedClients )
+ {
+ ExitLevel( );
+ return;
+ }
+
+ // map vote started
+ if( level.mapRotationVoteTime )
+ {
+ ExitLevel( );
+ return;
+ }
+
+ // see which players are ready
+ ready = 0;
+ notReady = 0;
+ readyMask = 0;
+ numPlayers = 0;
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE )
+ continue;
+
+ if( cl->readyToExit )
+ {
+ ready++;
+ if( i < 16 )
+ readyMask |= 1 << i;
+ }
+ else
+ notReady++;
+
+ numPlayers++;
+ }
+
+ trap_SetConfigstring( CS_CLIENTS_READY, va( "%d", readyMask ) );
+
+ // never exit in less than five seconds
+ if( level.time < level.intermissiontime + 5000 )
+ return;
+
+ // never let intermission go on for over 1 minute
+ if( level.time > level.intermissiontime + 60000 )
+ {
+ ExitLevel( );
+ return;
+ }
+
+ // if nobody wants to go, clear timer
+ if( !ready && numPlayers )
+ {
+ level.readyToExit = qfalse;
+ return;
+ }
+
+ // if everyone wants to go, go now
+ if( !notReady )
+ {
+ ExitLevel( );
+ return;
+ }
+
+ // if only a percent is needed to ready, check for it
+ if( g_readyPercent.integer && numPlayers &&
+ ready * 100 / numPlayers >= g_readyPercent.integer )
+ {
+ ExitLevel( );
+ return;
+ }
+
+ // the first person to ready starts the thirty second timeout
+ if( !level.readyToExit )
+ {
+ level.readyToExit = qtrue;
+ level.exitTime = level.time;
+ }
+
+ // if we have waited thirty seconds since at least one player
+ // wanted to exit, go ahead
+ if( level.time < level.exitTime + 30000 )
+ return;
+
+ ExitLevel( );
+}
+
+/*
+=============
+ScoreIsTied
+=============
+*/
+qboolean ScoreIsTied( void )
+{
+ int a, b;
+
+ if( level.numPlayingClients < 2 )
+ return qfalse;
+
+ a = level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ];
+ b = level.clients[ level.sortedClients[ 1 ] ].ps.persistant[ PERS_SCORE ];
+
+ return a == b;
+}
+
+/*
+=================
+CheckExitRules
+
+There will be a delay between the time the exit is qualified for
+and the time everyone is moved to the intermission spot, so you
+can see the last frag.
+=================
+*/
+void CheckExitRules( void )
+{
+ // if at the intermission, wait for all non-bots to
+ // signal ready, then go to next level
+ if( level.intermissiontime )
+ {
+ CheckIntermissionExit( );
+ return;
+ }
+
+ if( level.intermissionQueued )
+ {
+ if( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME )
+ {
+ level.intermissionQueued = 0;
+ BeginIntermission( );
+ }
+
+ return;
+ }
+
+ if( g_timelimit.integer )
+ {
+ if( level.time - level.startTime >= g_timelimit.integer * 60000 )
+ {
+ level.lastWin = PTE_NONE;
+ trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" );
+ trap_SetConfigstring( CS_WINNER, "Stalemate" );
+ LogExit( "Timelimit hit." );
+ G_admin_maplog_result( "t" );
+ return;
+ }
+ else if( level.time - level.startTime >= ( g_timelimit.integer - 5 ) * 60000 &&
+ level.timelimitWarning < TW_IMMINENT )
+ {
+ trap_SendServerCommand( -1, "cp \"5 minutes remaining!\"" );
+ level.timelimitWarning = TW_IMMINENT;
+ }
+ else if( level.time - level.startTime >= ( g_timelimit.integer - 1 ) * 60000 &&
+ level.timelimitWarning < TW_PASSED )
+ {
+ trap_SendServerCommand( -1, "cp \"1 minute remaining!\"" );
+ level.timelimitWarning = TW_PASSED;
+ }
+ }
+
+ if( level.uncondHumanWin ||
+ ( ( level.time > level.startTime + 1000 ) &&
+ ( level.numAlienSpawns == 0 ) &&
+ ( level.numLiveAlienClients == 0 ) ) )
+ {
+ //humans win
+ level.lastWin = PTE_HUMANS;
+ trap_SendServerCommand( -1, "print \"Humans win\n\"");
+ trap_SetConfigstring( CS_WINNER, "Humans Win" );
+ LogExit( "Humans win." );
+ G_admin_maplog_result( "h" );
+ }
+ else if( level.uncondAlienWin ||
+ ( ( level.time > level.startTime + 1000 ) &&
+ ( level.numHumanSpawns == 0 ) &&
+ ( level.numLiveHumanClients == 0 ) ) )
+ {
+ //aliens win
+ level.lastWin = PTE_ALIENS;
+ trap_SendServerCommand( -1, "print \"Aliens win\n\"");
+ trap_SetConfigstring( CS_WINNER, "Aliens Win" );
+ LogExit( "Aliens win." );
+ G_admin_maplog_result( "a" );
+ }
+}
+
+
+
+/*
+========================================================================
+
+FUNCTIONS CALLED EVERY FRAME
+
+========================================================================
+*/
+
+
+/*
+==================
+CheckVote
+==================
+*/
+void CheckVote( void )
+{
+ int votePassThreshold=level.votePassThreshold;
+ int voteYesPercent;
+
+ if( level.voteExecuteTime && level.voteExecuteTime < level.time )
+ {
+ level.voteExecuteTime = 0;
+
+ if( !Q_stricmp( level.voteString, "map_restart" ) )
+ {
+ G_admin_maplog_result( "r" );
+ }
+ else if( !Q_stricmpn( level.voteString, "map", 3 ) )
+ {
+ G_admin_maplog_result( "m" );
+ }
+
+
+ if( !Q_stricmp( level.voteString, "suddendeath" ) )
+ {
+ level.suddenDeathBeginTime = level.time + ( 1000 * g_suddenDeathVoteDelay.integer ) - level.startTime;
+
+ level.voteString[0] = '\0';
+
+ if( g_suddenDeathVoteDelay.integer )
+ trap_SendServerCommand( -1, va("cp \"Sudden Death will begin in %d seconds\n\"", g_suddenDeathVoteDelay.integer ) );
+ }
+
+ if( level.voteString[0] )
+ trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) );
+
+ if( !Q_stricmp( level.voteString, "map_restart" ) ||
+ !Q_stricmpn( level.voteString, "map", 3 ) )
+ {
+ level.restarted = qtrue;
+ }
+ }
+
+ if( !level.voteTime )
+ return;
+
+ if( level.voteYes + level.voteNo > 0 )
+ voteYesPercent = (int)( 100 * ( level.voteYes ) / ( level.voteYes + level.voteNo ) );
+ else
+ voteYesPercent = 0;
+
+ if( ( level.time - level.voteTime >= VOTE_TIME ) ||
+ ( level.voteYes + level.voteNo == level.numConnectedClients ) )
+ {
+ if( voteYesPercent> votePassThreshold || level.voteNo == 0 )
+ {
+ // execute the command, then remove the vote
+ trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"",
+ level.voteYes, level.voteNo ) );
+ G_LogPrintf( "Vote: Vote passed (%d-%d)\n", level.voteYes, level.voteNo );
+ level.voteExecuteTime = level.time + 3000;
+ }
+ else
+ {
+ // same behavior as a timeout
+ trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"",
+ level.voteYes, level.voteNo ) );
+ G_LogPrintf( "Vote: Vote failed (%d - %d)\n", level.voteYes, level.voteNo );
+ }
+ }
+ else
+ {
+ if( level.voteYes > (int)( (double) level.numConnectedClients *
+ ( (double) votePassThreshold/100.0 ) ) )
+ {
+ // execute the command, then remove the vote
+ trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"",
+ level.voteYes, level.voteNo ) );
+ G_LogPrintf( "Vote: Vote passed (%d - %d)\n", level.voteYes, level.voteNo );
+ level.voteExecuteTime = level.time + 3000;
+ }
+ else if( level.voteNo > (int)( (double) level.numConnectedClients *
+ ( (double) ( 100.0-votePassThreshold )/ 100.0 ) ) )
+ {
+ // same behavior as a timeout
+ trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"",
+ level.voteYes, level.voteNo ) );
+ G_LogPrintf("Vote failed\n");
+ }
+ else
+ {
+ // still waiting for a majority
+ return;
+ }
+ }
+
+ level.voteTime = 0;
+ trap_SetConfigstring( CS_VOTE_TIME, "" );
+ trap_SetConfigstring( CS_VOTE_STRING, "" );
+}
+
+
+/*
+==================
+CheckTeamVote
+==================
+*/
+void CheckTeamVote( int team )
+{
+ int cs_offset;
+
+ if ( team == PTE_HUMANS )
+ cs_offset = 0;
+ else if ( team == PTE_ALIENS )
+ cs_offset = 1;
+ else
+ return;
+
+ if( !level.teamVoteTime[ cs_offset ] )
+ return;
+
+ if( level.time - level.teamVoteTime[ cs_offset ] >= VOTE_TIME )
+ {
+ if( level.teamVoteYes[ cs_offset ] > level.teamVoteNo[ cs_offset ] && level.teamVoteYes[ cs_offset ] >= 2 )
+ {
+ // execute the command, then remove the vote
+ trap_SendServerCommand( -1, va("print \"Team vote passed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) );
+ trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) );
+ }
+ else
+ {
+ trap_SendServerCommand( -1, va("print \"Team vote failed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) );
+ G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] );
+ }
+ }
+ else
+ {
+ if( level.teamVoteYes[ cs_offset ] > level.numteamVotingClients[ cs_offset ] / 2 )
+ {
+ // execute the command, then remove the vote
+ trap_SendServerCommand( -1, va("print \"Team vote passed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) );
+ G_LogPrintf( "Teamvote: Team vote passed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] );
+ //
+ trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) );
+ }
+ else if( level.teamVoteNo[ cs_offset ] >= level.numteamVotingClients[ cs_offset ] / 2 )
+ {
+ // same behavior as a timeout
+ trap_SendServerCommand( -1, va("print \"Team vote failed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) );
+ G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] );
+ }
+ else
+ {
+ // still waiting for a majority
+ return;
+ }
+ }
+
+ level.teamVoteTime[ cs_offset ] = 0;
+ trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" );
+ trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, "" );
+}
+
+/*
+==================
+CheckMsgTimer
+==================
+*/
+void CheckMsgTimer( void )
+{
+ static int LastTime = 0;
+
+ if( level.time - LastTime < 1000 )
+ return;
+
+ LastTime = level.time;
+
+ if( level.mapRotationVoteTime )
+ {
+ G_IntermissionMapVoteMessageAll( );
+ return;
+ }
+
+ if( g_welcomeMsgTime.integer && g_welcomeMsg.string[ 0 ] )
+ {
+ char buffer[ MAX_STRING_CHARS ];
+ int wt;
+ int i;
+
+ buffer[ 0 ] = '\0';
+ wt = g_welcomeMsgTime.integer * 1000;
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected != CON_CONNECTED )
+ continue;
+
+ if( level.time - level.clients[ i ].pers.enterTime < wt )
+ {
+ if( buffer[ 0 ] == '\0' )
+ {
+ Q_strncpyz( buffer, g_welcomeMsg.string, sizeof( buffer ) );
+ G_ParseEscapedString( buffer );
+ }
+ trap_SendServerCommand( i, va( "cp \"%s\"", buffer ) );
+ }
+ }
+ }
+
+ if( !g_msgTime.integer )
+ return;
+
+ if( level.time - level.lastMsgTime < abs( g_msgTime.integer ) * 60000 )
+ return;
+
+ // negative settings only print once per map
+ if( ( level.lastMsgTime ) && g_msgTime.integer < 0 )
+ return;
+
+ level.lastMsgTime = level.time;
+
+ if( g_msg.string[0] )
+ {
+ char buffer[ MAX_STRING_CHARS ];
+
+ Q_strncpyz( buffer, g_msg.string, sizeof( buffer ) );
+ G_ParseEscapedString( buffer );
+ trap_SendServerCommand( -1, va( "cp \"%s\"", buffer ) );
+ trap_SendServerCommand( -1, va( "print \"%s\n\"", buffer ) );
+ }
+}
+
+/*
+==================
+CheckCountdown
+==================
+*/
+void CheckCountdown( void )
+{
+ static int lastmsg = 0;
+ int timeleft = g_warmup.integer - ( level.time - level.startTime ) / 1000;
+
+ if( !g_doWarmup.integer || timeleft < 0 )
+ return;
+
+ if( level.time - lastmsg < 1000 )
+ return;
+
+ lastmsg = level.time;
+ if( timeleft > 0 )
+ trap_SendServerCommand( -1, va( "cp \"^1Warmup Time:^7\n^%i----- ^7%i ^%i-----\"", timeleft % 7, timeleft, timeleft % 7 ) );
+ else if( timeleft == 0 )
+ trap_SendServerCommand( -1, "cp \"^2----- GO! -----^7\"" );
+}
+
+
+/*
+==================
+CheckCvars
+==================
+*/
+void CheckCvars( void )
+{
+ static int lastPasswordModCount = -1;
+ static int lastMarkDeconModCount = -1;
+ static int lastSDTimeModCount = -1;
+
+ if( g_password.modificationCount != lastPasswordModCount )
+ {
+ lastPasswordModCount = g_password.modificationCount;
+
+ if( *g_password.string && Q_stricmp( g_password.string, "none" ) )
+ trap_Cvar_Set( "g_needpass", "1" );
+ else
+ trap_Cvar_Set( "g_needpass", "0" );
+ }
+
+ // Unmark any structures for deconstruction when
+ // the server setting is changed
+ if( g_markDeconstruct.modificationCount != lastMarkDeconModCount )
+ {
+ int i;
+ gentity_t *ent;
+
+ lastMarkDeconModCount = g_markDeconstruct.modificationCount;
+
+ for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ ent->deconstruct = qfalse;
+ }
+ }
+
+ if( g_suddenDeathTime.modificationCount != lastSDTimeModCount )
+ {
+ lastSDTimeModCount = g_suddenDeathTime.modificationCount;
+ level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;
+ }
+
+ level.frameMsec = trap_Milliseconds( );
+}
+
+/*
+=============
+G_RunThink
+
+Runs thinking code for this frame if necessary
+=============
+*/
+void G_RunThink( gentity_t *ent )
+{
+ float thinktime;
+
+ thinktime = ent->nextthink;
+ if( thinktime <= 0 )
+ return;
+
+ if( thinktime > level.time )
+ return;
+
+ ent->nextthink = 0;
+ if( !ent->think )
+ G_Error( "NULL ent->think" );
+
+ ent->think( ent );
+}
+
+/*
+=============
+G_EvaluateAcceleration
+
+Calculates the acceleration for an entity
+=============
+*/
+void G_EvaluateAcceleration( gentity_t *ent, int msec )
+{
+ vec3_t deltaVelocity;
+ vec3_t deltaAccel;
+
+ VectorSubtract( ent->s.pos.trDelta, ent->oldVelocity, deltaVelocity );
+ VectorScale( deltaVelocity, 1.0f / (float)msec, ent->acceleration );
+
+ VectorSubtract( ent->acceleration, ent->oldAccel, deltaAccel );
+ VectorScale( deltaAccel, 1.0f / (float)msec, ent->jerk );
+
+ VectorCopy( ent->s.pos.trDelta, ent->oldVelocity );
+ VectorCopy( ent->acceleration, ent->oldAccel );
+}
+
+/*
+================
+G_RunFrame
+
+Advances the non-player objects in the world
+================
+*/
+void G_RunFrame( int levelTime )
+{
+ int i;
+ gentity_t *ent;
+ int msec;
+ int start, end;
+
+ // if we are waiting for the level to restart, do nothing
+ if( level.restarted )
+ return;
+
+ if( level.paused )
+ {
+ if( ( levelTime % 6000 ) == 0)
+ trap_SendServerCommand( -1, "cp \"^3Game is paused.\"" );
+
+ level.startTime += levelTime - level.time;
+ trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) );
+
+ if( levelTime - level.pauseTime > 3 * 60000 )
+ {
+ trap_SendConsoleCommand( EXEC_APPEND, "!unpause" );
+ }
+ }
+
+ CheckMsgTimer( );
+ CheckCountdown( );
+
+ level.framenum++;
+ level.previousTime = level.time;
+ level.time = levelTime;
+ msec = level.time - level.previousTime;
+
+ //TA: seed the rng
+ srand( level.framenum );
+
+ // get any cvar changes
+ G_UpdateCvars( );
+
+ //
+ // go through all allocated objects
+ //
+ start = trap_Milliseconds( );
+ ent = &g_entities[ 0 ];
+
+ for( i = 0; i < level.num_entities; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ // clear events that are too old
+ if( level.time - ent->eventTime > EVENT_VALID_MSEC )
+ {
+ if( ent->s.event )
+ {
+ ent->s.event = 0; // &= EV_EVENT_BITS;
+ if ( ent->client )
+ {
+ ent->client->ps.externalEvent = 0;
+ //ent->client->ps.events[0] = 0;
+ //ent->client->ps.events[1] = 0;
+ }
+ }
+
+ if( ent->freeAfterEvent )
+ {
+ // tempEntities or dropped items completely go away after their event
+ G_FreeEntity( ent );
+ continue;
+ }
+ else if( ent->unlinkAfterEvent )
+ {
+ // items that will respawn will hide themselves after their pickup event
+ ent->unlinkAfterEvent = qfalse;
+ trap_UnlinkEntity( ent );
+ }
+ }
+
+ // temporary entities don't think
+ if( ent->freeAfterEvent )
+ continue;
+
+ //TA: calculate the acceleration of this entity
+ if( ent->evaluateAcceleration )
+ G_EvaluateAcceleration( ent, msec );
+
+ if( !ent->r.linked && ent->neverFree )
+ continue;
+
+ if( ent->s.eType == ET_MISSILE )
+ {
+ G_RunMissile( ent );
+ continue;
+ }
+
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ G_BuildableThink( ent, msec );
+ continue;
+ }
+
+ if( ent->s.eType == ET_CORPSE || ent->physicsObject )
+ {
+ G_Physics( ent, msec );
+ continue;
+ }
+
+ if( ent->s.eType == ET_MOVER )
+ {
+ G_RunMover( ent );
+ continue;
+ }
+
+ if( i < MAX_CLIENTS )
+ {
+ G_RunClient( ent );
+ continue;
+ }
+
+ G_RunThink( ent );
+ }
+ end = trap_Milliseconds();
+
+ start = trap_Milliseconds();
+
+ // perform final fixups on the players
+ ent = &g_entities[ 0 ];
+
+ for( i = 0; i < level.maxclients; i++, ent++ )
+ {
+ if( ent->inuse )
+ ClientEndFrame( ent );
+ }
+
+ // save position information for all active clients
+ G_UnlaggedStore( );
+
+ end = trap_Milliseconds();
+
+ //TA:
+ G_CountSpawns( );
+ G_CalculateBuildPoints( );
+ G_CalculateStages( );
+ G_SpawnClients( PTE_ALIENS );
+ G_SpawnClients( PTE_HUMANS );
+ G_CalculateAvgPlayers( );
+ G_UpdateZaps( msec );
+
+ // see if it is time to end the level
+ CheckExitRules( );
+
+ // update to team status?
+ CheckTeamStatus( );
+
+ // cancel vote if timed out
+ CheckVote( );
+
+ // check team votes
+ CheckTeamVote( PTE_HUMANS );
+ CheckTeamVote( PTE_ALIENS );
+
+ // for tracking changes
+ CheckCvars( );
+
+ if( g_listEntity.integer )
+ {
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ G_Printf( "%4i: %s\n", i, g_entities[ i ].classname );
+
+ trap_Cvar_Set( "g_listEntity", "0" );
+ }
+}
+
diff --git a/src/game/g_maprotation.c b/src/game/g_maprotation.c
new file mode 100644
index 0000000..a9d0261
--- /dev/null
+++ b/src/game/g_maprotation.c
@@ -0,0 +1,1318 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// g_maprotation.c -- the map rotation system
+
+#include "g_local.h"
+
+mapRotations_t mapRotations;
+
+
+static qboolean G_GetVotedMap( char *name, int size, int rotation, int map );
+
+/*
+===============
+G_MapExists
+
+Check if a map exists
+===============
+*/
+qboolean G_MapExists( char *name )
+{
+ return trap_FS_FOpenFile( va( "maps/%s.bsp", name ), NULL, FS_READ );
+}
+
+/*
+===============
+G_RotationExists
+
+Check if a rotation exists
+===============
+*/
+static qboolean G_RotationExists( char *name )
+{
+ int i;
+
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ if( Q_strncmp( mapRotations.rotations[ i ].name, name, MAX_QPATH ) == 0 )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+G_ParseCommandSection
+
+Parse a map rotation command section
+===============
+*/
+static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p )
+{
+ char *token;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this command section
+
+ if( !Q_stricmp( token, "layouts" ) )
+ {
+ token = COM_ParseExt( text_p, qfalse );
+ mre->layouts[ 0 ] = '\0';
+ while( token && token[ 0 ] != 0 )
+ {
+ Q_strcat( mre->layouts, sizeof( mre->layouts ), token );
+ Q_strcat( mre->layouts, sizeof( mre->layouts ), " " );
+ token = COM_ParseExt( text_p, qfalse );
+ }
+ continue;
+ }
+
+ Q_strncpyz( mre->postCmds[ mre->numCmds ], token, sizeof( mre->postCmds[ 0 ] ) );
+ Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " );
+
+ token = COM_ParseExt( text_p, qfalse );
+
+ while( token && token[ 0 ] != 0 )
+ {
+ Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), token );
+ Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " );
+ token = COM_ParseExt( text_p, qfalse );
+ }
+
+ if( mre->numCmds == MAX_MAP_COMMANDS )
+ {
+ G_Printf( S_COLOR_RED "ERROR: maximum number of map commands (%d) reached\n",
+ MAX_MAP_COMMANDS );
+ return qfalse;
+ }
+ else
+ mre->numCmds++;
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+G_ParseMapRotation
+
+Parse a map rotation section
+===============
+*/
+static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p )
+{
+ char *token;
+ qboolean mnSet = qfalse;
+ mapRotationEntry_t *mre = NULL;
+ mapRotationCondition_t *mrc;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !mnSet )
+ {
+ G_Printf( S_COLOR_RED "ERROR: map settings section with no name\n" );
+ return qfalse;
+ }
+
+ if( !G_ParseMapCommandSection( mre, text_p ) )
+ {
+ G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" );
+ return qfalse;
+ }
+
+ mnSet = qfalse;
+ continue;
+ }
+ else if( !Q_stricmp( token, "goto" ) )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ mrc = &mre->conditions[ mre->numConditions ];
+ mrc->unconditional = qtrue;
+ Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
+
+ if( mre->numConditions == MAX_MAP_ROTATION_CONDS )
+ {
+ G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n",
+ MAX_MAP_ROTATION_CONDS );
+ return qfalse;
+ }
+ else
+ mre->numConditions++;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "if" ) )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ mrc = &mre->conditions[ mre->numConditions ];
+
+ if( !Q_stricmp( token, "numClients" ) )
+ {
+ mrc->lhs = MCV_NUMCLIENTS;
+
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "<" ) )
+ mrc->op = MCO_LT;
+ else if( !Q_stricmp( token, ">" ) )
+ mrc->op = MCO_GT;
+ else if( !Q_stricmp( token, "=" ) )
+ mrc->op = MCO_EQ;
+ else
+ {
+ G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token );
+ return qfalse;
+ }
+
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ mrc->numClients = atoi( token );
+ }
+ else if( !Q_stricmp( token, "lastWin" ) )
+ {
+ mrc->lhs = MCV_LASTWIN;
+
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "aliens" ) )
+ mrc->lastWin = PTE_ALIENS;
+ else if( !Q_stricmp( token, "humans" ) )
+ mrc->lastWin = PTE_HUMANS;
+ else
+ {
+ G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token );
+ return qfalse;
+ }
+ }
+ else if( !Q_stricmp( token, "random" ) )
+ mrc->lhs = MCV_RANDOM;
+ else
+ {
+ G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token );
+ return qfalse;
+ }
+
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ mrc->unconditional = qfalse;
+ Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
+
+ if( mre->numConditions == MAX_MAP_ROTATION_CONDS )
+ {
+ G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n",
+ MAX_MAP_ROTATION_CONDS );
+ return qfalse;
+ }
+ else
+ mre->numConditions++;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "*VOTE*" ) )
+ {
+ if( mr->numMaps == MAX_MAP_ROTATION_MAPS )
+ {
+ G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n",
+ MAX_MAP_ROTATION_MAPS );
+ return qfalse;
+ }
+ mre = &mr->maps[ mr->numMaps ];
+ Q_strncpyz( mre->name, token, sizeof( mre->name ) );
+
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "}" ) )
+ {
+ break;
+ }
+ else
+ {
+ if( mre->numConditions < MAX_MAP_ROTATION_CONDS )
+ {
+ mrc = &mre->conditions[ mre->numConditions ];
+ mrc->lhs = MCV_VOTE;
+ mrc->unconditional = qfalse;
+ Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
+
+ mre->numConditions++;
+ }
+ else
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: maximum number of maps for one vote (%d) reached\n",
+ MAX_MAP_ROTATION_CONDS );
+ }
+ }
+ }
+ if( !mre->numConditions )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: no maps in *VOTE* section\n" );
+ }
+ else
+ {
+ mr->numMaps++;
+ mnSet = qtrue;
+ }
+ }
+ else
+ {
+ G_Printf( S_COLOR_RED "ERROR: *VOTE* with no section\n" );
+ return qfalse;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "*RANDOM*" ) )
+ {
+ if( mr->numMaps == MAX_MAP_ROTATION_MAPS )
+ {
+ G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n",
+ MAX_MAP_ROTATION_MAPS );
+ return qfalse;
+ }
+ mre = &mr->maps[ mr->numMaps ];
+ Q_strncpyz( mre->name, token, sizeof( mre->name ) );
+
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "}" ) )
+ {
+ break;
+ }
+ else
+ {
+ if( mre->numConditions < MAX_MAP_ROTATION_CONDS )
+ {
+ mrc = &mre->conditions[ mre->numConditions ];
+ mrc->lhs = MCV_SELECTEDRANDOM;
+ mrc->unconditional = qfalse;
+ Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
+
+ mre->numConditions++;
+ }
+ else
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: maximum number of maps for one Random Slot (%d) reached\n",
+ MAX_MAP_ROTATION_CONDS );
+ }
+ }
+ }
+ if( !mre->numConditions )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: no maps in *RANDOM* section\n" );
+ }
+ else
+ {
+ mr->numMaps++;
+ mnSet = qtrue;
+ }
+ }
+ else
+ {
+ G_Printf( S_COLOR_RED "ERROR: *RANDOM* with no section\n" );
+ return qfalse;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this map rotation
+
+ mre = &mr->maps[ mr->numMaps ];
+
+ if( mr->numMaps == MAX_MAP_ROTATION_MAPS )
+ {
+ G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n",
+ MAX_MAP_ROTATION_MAPS );
+ return qfalse;
+ }
+ else
+ mr->numMaps++;
+
+ Q_strncpyz( mre->name, token, sizeof( mre->name ) );
+ mnSet = qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+G_ParseMapRotationFile
+
+Load the map rotations from a map rotation file
+===============
+*/
+static qboolean G_ParseMapRotationFile( const char *fileName )
+{
+ char *text_p;
+ int i, j, k;
+ int len;
+ char *token;
+ char text[ 20000 ];
+ char mrName[ MAX_QPATH ];
+ qboolean mrNameSet = qfalse;
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+ if( len < 0 )
+ return qfalse;
+
+ if( len == 0 || len >= sizeof( text ) - 1 )
+ {
+ trap_FS_FCloseFile( f );
+ G_Printf( S_COLOR_RED "ERROR: map rotation file %s is %s\n", fileName,
+ len == 0 ? "empty" : "too long" );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( mrNameSet )
+ {
+ //check for name space clashes
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ if( !Q_stricmp( mapRotations.rotations[ i ].name, mrName ) )
+ {
+ G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName );
+ return qfalse;
+ }
+ }
+
+ Q_strncpyz( mapRotations.rotations[ mapRotations.numRotations ].name, mrName, MAX_QPATH );
+
+ if( !G_ParseMapRotation( &mapRotations.rotations[ mapRotations.numRotations ], &text_p ) )
+ {
+ G_Printf( S_COLOR_RED "ERROR: %s: failed to parse map rotation %s\n", fileName, mrName );
+ return qfalse;
+ }
+
+ //start parsing map rotations again
+ mrNameSet = qfalse;
+
+ if( mapRotations.numRotations == MAX_MAP_ROTATIONS )
+ {
+ G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n",
+ MAX_MAP_ROTATIONS );
+ return qfalse;
+ }
+ else
+ mapRotations.numRotations++;
+
+ continue;
+ }
+ else
+ {
+ G_Printf( S_COLOR_RED "ERROR: unamed map rotation\n" );
+ return qfalse;
+ }
+ }
+
+ if( !mrNameSet )
+ {
+ Q_strncpyz( mrName, token, sizeof( mrName ) );
+ mrNameSet = qtrue;
+ }
+ else
+ {
+ G_Printf( S_COLOR_RED "ERROR: map rotation already named\n" );
+ return qfalse;
+ }
+ }
+
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ )
+ {
+ if( Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*VOTE*") != 0 &&
+ Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*") != 0 &&
+ !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) )
+ {
+ G_Printf( S_COLOR_RED "ERROR: map \"%s\" doesn't exist\n",
+ mapRotations.rotations[ i ].maps[ j ].name );
+ return qfalse;
+ }
+
+ for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ )
+ {
+ if( !G_MapExists( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ) &&
+ !G_RotationExists( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ) )
+ {
+ G_Printf( S_COLOR_RED "ERROR: conditional destination \"%s\" doesn't exist\n",
+ mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest );
+ return qfalse;
+ }
+
+ }
+
+ }
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+G_PrintRotations
+
+Print the parsed map rotations
+===============
+*/
+void G_PrintRotations( void )
+{
+ int i, j, k;
+
+ G_Printf( "Map rotations as parsed:\n\n" );
+
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ G_Printf( "rotation: %s\n{\n", mapRotations.rotations[ i ].name );
+
+ for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ )
+ {
+ G_Printf( " map: %s\n {\n", mapRotations.rotations[ i ].maps[ j ].name );
+
+ for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numCmds; k++ )
+ {
+ G_Printf( " command: %s\n",
+ mapRotations.rotations[ i ].maps[ j ].postCmds[ k ] );
+ }
+
+ G_Printf( " }\n" );
+
+ for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ )
+ {
+ G_Printf( " conditional: %s\n",
+ mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest );
+ }
+
+ }
+
+ G_Printf( "}\n" );
+ }
+
+ G_Printf( "Total memory used: %d bytes\n", sizeof( mapRotations ) );
+}
+
+/*
+===============
+G_GetCurrentMapArray
+
+Fill a static array with the current map of each rotation
+===============
+*/
+static int *G_GetCurrentMapArray( void )
+{
+ static int currentMap[ MAX_MAP_ROTATIONS ];
+ int i = 0;
+ char text[ MAX_MAP_ROTATIONS * 2 ];
+ char *text_p, *token;
+
+ Q_strncpyz( text, g_currentMap.string, sizeof( text ) );
+
+ text_p = text;
+
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ currentMap[ i++ ] = atoi( token );
+ }
+
+ return currentMap;
+}
+
+/*
+===============
+G_SetCurrentMap
+
+Set the current map in some rotation
+===============
+*/
+static void G_SetCurrentMap( int currentMap, int rotation )
+{
+ char text[ MAX_MAP_ROTATIONS * 2 ] = { 0 };
+ int *p = G_GetCurrentMapArray( );
+ int i;
+
+ p[ rotation ] = currentMap;
+
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ Q_strcat( text, sizeof( text ), va( "%d ", p[ i ] ) );
+
+ trap_Cvar_Set( "g_currentMap", text );
+ trap_Cvar_Update( &g_currentMap );
+}
+
+/*
+===============
+G_GetCurrentMap
+
+Return the current map in some rotation
+===============
+*/
+int G_GetCurrentMap( int rotation )
+{
+ int *p = G_GetCurrentMapArray( );
+
+ return p[ rotation ];
+}
+
+static qboolean G_GetRandomMap( char *name, int size, int rotation, int map );
+
+/*
+===============
+G_IssueMapChange
+
+Send commands to the server to actually change the map
+===============
+*/
+static void G_IssueMapChange( int rotation )
+{
+ int i;
+ int map = G_GetCurrentMap( rotation );
+ char cmd[ MAX_TOKEN_CHARS ];
+ char newmap[ MAX_CVAR_VALUE_STRING ];
+
+ Q_strncpyz( newmap, mapRotations.rotations[rotation].maps[map].name, sizeof( newmap ));
+
+ if (!Q_stricmp( newmap, "*VOTE*") )
+ {
+ fileHandle_t f;
+
+ G_GetVotedMap( newmap, sizeof( newmap ), rotation, map );
+ if( trap_FS_FOpenFile( va("maps/%s.bsp", newmap), &f, FS_READ ) > 0 )
+ {
+ trap_FS_FCloseFile( f );
+ }
+ else
+ {
+ G_AdvanceMapRotation();
+ return;
+ }
+ }
+ else if(!Q_stricmp( newmap, "*RANDOM*") )
+ {
+ fileHandle_t f;
+
+ G_GetRandomMap( newmap, sizeof( newmap ), rotation, map );
+ if( trap_FS_FOpenFile( va("maps/%s.bsp", newmap), &f, FS_READ ) > 0 )
+ {
+ trap_FS_FCloseFile( f );
+ }
+ else
+ {
+ G_AdvanceMapRotation();
+ return;
+ }
+ }
+
+ // allow a manually defined g_layouts setting to override the maprotation
+ if( !g_layouts.string[ 0 ] &&
+ mapRotations.rotations[ rotation ].maps[ map ].layouts[ 0 ] )
+ {
+ trap_Cvar_Set( "g_layouts",
+ mapRotations.rotations[ rotation ].maps[ map ].layouts );
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n",
+ newmap ) );
+
+ // load up map defaults if g_mapConfigs is set
+ G_MapConfigs( newmap );
+
+ for( i = 0; i < mapRotations.rotations[ rotation ].maps[ map ].numCmds; i++ )
+ {
+ Q_strncpyz( cmd, mapRotations.rotations[ rotation ].maps[ map ].postCmds[ i ],
+ sizeof( cmd ) );
+ Q_strcat( cmd, sizeof( cmd ), "\n" );
+ trap_SendConsoleCommand( EXEC_APPEND, cmd );
+ }
+}
+
+/*
+===============
+G_ResolveConditionDestination
+
+Resolve the destination of some condition
+===============
+*/
+static mapConditionType_t G_ResolveConditionDestination( int *n, char *name )
+{
+ int i;
+
+ //search the current rotation first...
+ for( i = 0; i < mapRotations.rotations[ g_currentMapRotation.integer ].numMaps; i++ )
+ {
+ if( !Q_stricmp( mapRotations.rotations[ g_currentMapRotation.integer ].maps[ i ].name, name ) )
+ {
+ *n = i;
+ return MCT_MAP;
+ }
+ }
+
+ //...then search the rotation names
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) )
+ {
+ *n = i;
+ return MCT_ROTATION;
+ }
+ }
+
+ return MCT_ERR;
+}
+
+/*
+===============
+G_EvaluateMapCondition
+
+Evaluate a map condition
+===============
+*/
+static qboolean G_EvaluateMapCondition( mapRotationCondition_t *mrc )
+{
+ switch( mrc->lhs )
+ {
+ case MCV_RANDOM:
+ return rand( ) & 1;
+ break;
+
+ case MCV_NUMCLIENTS:
+ switch( mrc->op )
+ {
+ case MCO_LT:
+ return level.numConnectedClients < mrc->numClients;
+ break;
+
+ case MCO_GT:
+ return level.numConnectedClients > mrc->numClients;
+ break;
+
+ case MCO_EQ:
+ return level.numConnectedClients == mrc->numClients;
+ break;
+ }
+ break;
+
+ case MCV_LASTWIN:
+ return level.lastWin == mrc->lastWin;
+ break;
+
+ case MCV_VOTE:
+ // ignore vote for conditions;
+ break;
+ case MCV_SELECTEDRANDOM:
+ // ignore vote for conditions;
+ break;
+
+ default:
+ case MCV_ERR:
+ G_Printf( S_COLOR_RED "ERROR: malformed map switch condition\n" );
+ break;
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+G_AdvanceMapRotation
+
+Increment the current map rotation
+===============
+*/
+qboolean G_AdvanceMapRotation( void )
+{
+ mapRotation_t *mr;
+ mapRotationEntry_t *mre;
+ mapRotationCondition_t *mrc;
+ int currentRotation, currentMap, nextMap;
+ int i, n;
+ mapConditionType_t mct;
+
+ if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING )
+ return qfalse;
+
+ currentMap = G_GetCurrentMap( currentRotation );
+
+ mr = &mapRotations.rotations[ currentRotation ];
+ mre = &mr->maps[ currentMap ];
+ nextMap = ( currentMap + 1 ) % mr->numMaps;
+
+ for( i = 0; i < mre->numConditions; i++ )
+ {
+ mrc = &mre->conditions[ i ];
+
+ if( mrc->unconditional || G_EvaluateMapCondition( mrc ) )
+ {
+ mct = G_ResolveConditionDestination( &n, mrc->dest );
+
+ switch( mct )
+ {
+ case MCT_MAP:
+ nextMap = n;
+ break;
+
+ case MCT_ROTATION:
+ //need to increment the current map before changing the rotation
+ //or you get infinite loops with some conditionals
+ G_SetCurrentMap( nextMap, currentRotation );
+ G_StartMapRotation( mrc->dest, qtrue );
+ return qtrue;
+ break;
+
+ default:
+ case MCT_ERR:
+ G_Printf( S_COLOR_YELLOW "WARNING: map switch destination could not be resolved: %s\n",
+ mrc->dest );
+ break;
+ }
+ }
+ }
+
+ G_SetCurrentMap( nextMap, currentRotation );
+ G_IssueMapChange( currentRotation );
+
+ return qtrue;
+}
+
+/*
+===============
+G_StartMapRotation
+
+Switch to a new map rotation
+===============
+*/
+qboolean G_StartMapRotation( char *name, qboolean changeMap )
+{
+ int i;
+
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) )
+ {
+ trap_Cvar_Set( "g_currentMapRotation", va( "%d", i ) );
+ trap_Cvar_Update( &g_currentMapRotation );
+
+ if( changeMap )
+ G_IssueMapChange( i );
+ break;
+ }
+ }
+
+ if( i == mapRotations.numRotations )
+ return qfalse;
+ else
+ return qtrue;
+}
+
+/*
+===============
+G_StopMapRotation
+
+Stop the current map rotation
+===============
+*/
+void G_StopMapRotation( void )
+{
+ trap_Cvar_Set( "g_currentMapRotation", va( "%d", NOT_ROTATING ) );
+ trap_Cvar_Update( &g_currentMapRotation );
+}
+
+/*
+===============
+G_MapRotationActive
+
+Test if any map rotation is currently active
+===============
+*/
+qboolean G_MapRotationActive( void )
+{
+ return ( g_currentMapRotation.integer != NOT_ROTATING );
+}
+
+/*
+===============
+G_InitMapRotations
+
+Load and intialise the map rotations
+===============
+*/
+void G_InitMapRotations( void )
+{
+ const char *fileName = "maprotation.cfg";
+
+ //load the file if it exists
+ if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) )
+ {
+ if( !G_ParseMapRotationFile( fileName ) )
+ G_Printf( S_COLOR_RED "ERROR: failed to parse %s file\n", fileName );
+ }
+ else
+ G_Printf( "%s file not found.\n", fileName );
+
+ if( g_currentMapRotation.integer == NOT_ROTATING )
+ {
+ if( g_initialMapRotation.string[ 0 ] != 0 )
+ {
+ G_StartMapRotation( g_initialMapRotation.string, qfalse );
+
+ trap_Cvar_Set( "g_initialMapRotation", "" );
+ trap_Cvar_Update( &g_initialMapRotation );
+ }
+ }
+}
+
+static char rotationVoteList[ MAX_MAP_ROTATION_CONDS ][ MAX_QPATH ];
+static int rotationVoteLen = 0;
+
+static int rotationVoteClientPosition[ MAX_CLIENTS ];
+static int rotationVoteClientSelection[ MAX_CLIENTS ];
+
+/*
+===============
+G_CheckMapRotationVote
+===============
+*/
+qboolean G_CheckMapRotationVote( void )
+{
+ mapRotation_t *mr;
+ mapRotationEntry_t *mre;
+ mapRotationCondition_t *mrc;
+ int currentRotation, currentMap, nextMap;
+ int i;
+
+ rotationVoteLen = 0;
+
+ if( g_mapRotationVote.integer < 1 )
+ return qfalse;
+
+ if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING )
+ return qfalse;
+
+ currentMap = G_GetCurrentMap( currentRotation );
+
+ mr = &mapRotations.rotations[ currentRotation ];
+ nextMap = ( currentMap + 1 ) % mr->numMaps;
+ mre = &mr->maps[ nextMap ];
+
+ for( i = 0; i < mre->numConditions; i++ )
+ {
+ mrc = &mre->conditions[ i ];
+
+ if( mrc->lhs == MCV_VOTE )
+ {
+ Q_strncpyz( rotationVoteList[ rotationVoteLen ], mrc->dest,
+ sizeof( rotationVoteList[ rotationVoteLen ] ) );
+ rotationVoteLen++;
+ if( rotationVoteLen >= MAX_MAP_ROTATION_CONDS )
+ break;
+ }
+ }
+
+ if( !rotationVoteLen )
+ return qfalse;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ rotationVoteClientPosition[ i ] = 0;
+ rotationVoteClientSelection[ i ] = -1;
+ }
+
+ return qtrue;
+}
+
+typedef struct {
+ int votes;
+ int map;
+} MapVoteResultsSort_t;
+
+static int SortMapVoteResults( const void *av, const void *bv )
+{
+ const MapVoteResultsSort_t *a = av;
+ const MapVoteResultsSort_t *b = bv;
+
+ if( a->votes > b->votes )
+ return -1;
+ if( a->votes < b->votes )
+ return 1;
+
+ if( a->map > b->map )
+ return 1;
+ if( a->map < b->map )
+ return -1;
+
+ return 0;
+}
+
+static int G_GetMapVoteWinner( int *winvotes, int *totalvotes, int *resultorder )
+{
+ MapVoteResultsSort_t results[ MAX_MAP_ROTATION_CONDS ];
+ int tv = 0;
+ int i, n;
+
+ for( i = 0; i < MAX_MAP_ROTATION_CONDS; i++ )
+ {
+ results[ i ].votes = 0;
+ results[ i ].map = i;
+ }
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ n = rotationVoteClientSelection[ i ];
+ if( n >=0 && n < MAX_MAP_ROTATION_CONDS )
+ {
+ results[ n ].votes += 1;
+ tv++;
+ }
+ }
+
+ qsort ( results, MAX_MAP_ROTATION_CONDS, sizeof( results[ 0 ] ), SortMapVoteResults );
+
+ if( winvotes != NULL )
+ *winvotes = results[ 0 ].votes;
+ if( totalvotes != NULL )
+ *totalvotes = tv;
+
+ if( resultorder != NULL )
+ {
+ for( i = 0; i < MAX_MAP_ROTATION_CONDS; i++ )
+ resultorder[ results[ i ].map ] = i;
+ }
+
+ return results[ 0 ].map;
+}
+
+qboolean G_IntermissionMapVoteWinner( void )
+{
+ int winner, winvotes, totalvotes;
+ int nonvotes;
+
+ winner = G_GetMapVoteWinner( &winvotes, &totalvotes, NULL );
+ if( winvotes * 2 > level.numConnectedClients )
+ return qtrue;
+ nonvotes = level.numConnectedClients - totalvotes;
+ if( nonvotes < 0 )
+ nonvotes = 0;
+ if( winvotes > nonvotes + ( totalvotes - winvotes ) )
+ return qtrue;
+
+ return qfalse;
+}
+
+static qboolean G_GetVotedMap( char *name, int size, int rotation, int map )
+{
+ mapRotation_t *mr;
+ mapRotationEntry_t *mre;
+ mapRotationCondition_t *mrc;
+ int i, n;
+ int winner;
+ qboolean found = qfalse;
+
+ if( !rotationVoteLen )
+ return qfalse;
+
+ winner = G_GetMapVoteWinner( NULL, NULL, NULL );
+
+ mr = &mapRotations.rotations[ rotation ];
+ mre = &mr->maps[ map ];
+
+ n = 0;
+ for( i = 0; i < mre->numConditions && n < rotationVoteLen; i++ )
+ {
+ mrc = &mre->conditions[ i ];
+
+ if( mrc->lhs == MCV_VOTE )
+ {
+ if( n == winner )
+ {
+ Q_strncpyz( name, mrc->dest, size );
+ found = qtrue;
+ break;
+ }
+ n++;
+ }
+ }
+
+ rotationVoteLen = 0;
+
+ return found;
+}
+
+static void G_IntermissionMapVoteMessageReal( gentity_t *ent, int winner, int winvotes, int totalvotes, int *ranklist )
+{
+ int clientNum;
+ char string[ MAX_STRING_CHARS ];
+ char entry[ MAX_STRING_CHARS ];
+ int ourlist[ MAX_MAP_ROTATION_CONDS ];
+ int len = 0;
+ int index, selection;
+ int i;
+ char *color;
+ char *rank;
+
+ clientNum = ent-g_entities;
+
+ index = rotationVoteClientSelection[ clientNum ];
+ selection = rotationVoteClientPosition[ clientNum ];
+
+ if( winner < 0 || winner >= MAX_MAP_ROTATION_CONDS || ranklist == NULL )
+ {
+ ranklist = &ourlist[0];
+ winner = G_GetMapVoteWinner( &winvotes, &totalvotes, ranklist );
+ }
+
+ Q_strncpyz( string, "^7Attack = down ^0/^7 Repair = up ^0/^7 F1 = vote\n\n"
+ "^2Map Vote Menu\n"
+ "^7+------------------+\n", sizeof( string ) );
+ for( i = 0; i < rotationVoteLen; i++ )
+ {
+ if( !G_MapExists( rotationVoteList[ i ] ) )
+ continue;
+
+ if( i == selection )
+ color = "^5";
+ else if( i == index )
+ color = "^1";
+ else
+ color = "^7";
+
+ switch( ranklist[ i ] )
+ {
+ case 0:
+ rank = "^7---";
+ break;
+ case 1:
+ rank = "^7--";
+ break;
+ case 2:
+ rank = "^7-";
+ break;
+ default:
+ rank = "";
+ break;
+ }
+
+ Com_sprintf( entry, sizeof( entry ), "^7%s%s%s%s %s %s%s^7%s\n",
+ ( i == index ) ? "^1>>>" : "",
+ ( i == selection ) ? "^7(" : " ",
+ rank,
+ color,
+ rotationVoteList[ i ],
+ rank,
+ ( i == selection ) ? "^7)" : " ",
+ ( i == index ) ? "^1<<<" : "" );
+
+ Q_strcat( string, sizeof( string ), entry );
+ len += strlen( entry );
+ }
+
+ Com_sprintf( entry, sizeof( entry ),
+ "\n^7+----------------+\nleader: ^3%s^7 with %d vote%s\nvoters: %d\ntime left: %d",
+ rotationVoteList[ winner ],
+ winvotes,
+ ( winvotes == 1 ) ? "" : "s",
+ totalvotes,
+ ( level.mapRotationVoteTime - level.time ) / 1000 );
+ Q_strcat( string, sizeof( string ), entry );
+
+ trap_SendServerCommand( ent-g_entities, va( "cp \"%s\"\n", string ) );
+}
+
+void G_IntermissionMapVoteMessageAll( void )
+{
+ int ranklist[ MAX_MAP_ROTATION_CONDS ];
+ int winner;
+ int winvotes, totalvotes;
+ int i;
+
+ winner = G_GetMapVoteWinner( &winvotes, &totalvotes, &ranklist[ 0 ] );
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_CONNECTED )
+ G_IntermissionMapVoteMessageReal( g_entities + i, winner, winvotes, totalvotes, ranklist );
+ }
+}
+
+void G_IntermissionMapVoteMessage( gentity_t *ent )
+{
+ G_IntermissionMapVoteMessageReal( ent, -1, 0, 0, NULL );
+}
+
+void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose )
+{
+ int clientNum;
+ int n;
+
+ clientNum = ent-g_entities;
+
+ if( choose )
+ {
+ rotationVoteClientSelection[ clientNum ] = rotationVoteClientPosition[ clientNum ];
+ }
+ else
+ {
+ n = rotationVoteClientPosition[ clientNum ];
+ if( next )
+ n++;
+ else
+ n--;
+
+ if( n >= rotationVoteLen )
+ n = rotationVoteLen - 1;
+ if( n < 0 )
+ n = 0;
+
+ rotationVoteClientPosition[ clientNum ] = n;
+ }
+
+ G_IntermissionMapVoteMessage( ent );
+}
+
+static qboolean G_GetRandomMap( char *name, int size, int rotation, int map )
+{
+ mapRotation_t *mr;
+ mapRotationEntry_t *mre;
+ mapRotationCondition_t *mrc;
+ int i, nummaps;
+ int randompick = 0;
+ int maplist[ 32 ];
+
+ mr = &mapRotations.rotations[ rotation ];
+ mre = &mr->maps[ map ];
+
+ nummaps = 0;
+ //count the number of map votes
+ for( i = 0; i < mre->numConditions; i++ )
+ {
+ mrc = &mre->conditions[ i ];
+
+ if( mrc->lhs == MCV_SELECTEDRANDOM )
+ {
+ //map doesnt exist
+ if( !G_MapExists( mrc->dest ) ) {
+ continue;
+ }
+ maplist[ nummaps ] = i;
+ nummaps++;
+ }
+ }
+
+ if( nummaps == 0 ) {
+ return qfalse;
+ }
+
+ randompick = (int)( random() * nummaps );
+
+ Q_strncpyz( name, mre->conditions[ maplist[ randompick ] ].dest, size );
+
+ return qtrue;
+}
diff --git a/src/game/g_mem.c b/src/game/g_mem.c
new file mode 100644
index 0000000..6935194
--- /dev/null
+++ b/src/game/g_mem.c
@@ -0,0 +1,216 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+#define POOLSIZE ( 1024 * 1024 )
+#define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value
+#define ROUNDBITS 31 // Round to 32 bytes
+
+struct freememnode
+{
+ // Size of ROUNDBITS
+ int cookie, size; // Size includes node (obviously)
+ struct freememnode *prev, *next;
+};
+
+static char memoryPool[POOLSIZE];
+static struct freememnode *freehead;
+static int freemem;
+
+void *G_Alloc( int size )
+{
+ // Find a free block and allocate.
+ // Does two passes, attempts to fill same-sized free slot first.
+
+ struct freememnode *fmn, *prev, *next, *smallest;
+ int allocsize, smallestsize;
+ char *endptr;
+ int *ptr;
+
+ allocsize = ( size + sizeof(int) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary
+ ptr = NULL;
+
+ smallest = NULL;
+ smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :)
+ for( fmn = freehead; fmn; fmn = fmn->next )
+ {
+ if( fmn->cookie != FREEMEMCOOKIE )
+ G_Error( "G_Alloc: Memory corruption detected!\n" );
+
+ if( fmn->size >= allocsize )
+ {
+ // We've got a block
+ if( fmn->size == allocsize )
+ {
+ // Same size, just remove
+
+ prev = fmn->prev;
+ next = fmn->next;
+ if( prev )
+ prev->next = next; // Point previous node to next
+ if( next )
+ next->prev = prev; // Point next node to previous
+ if( fmn == freehead )
+ freehead = next; // Set head pointer to next
+ ptr = (int *) fmn;
+ break; // Stop the loop, this is fine
+ }
+ else
+ {
+ // Keep track of the smallest free slot
+ if( fmn->size < smallestsize )
+ {
+ smallest = fmn;
+ smallestsize = fmn->size;
+ }
+ }
+ }
+ }
+
+ if( !ptr && smallest )
+ {
+ // We found a slot big enough
+ smallest->size -= allocsize;
+ endptr = (char *) smallest + smallest->size;
+ ptr = (int *) endptr;
+ }
+
+ if( ptr )
+ {
+ freemem -= allocsize;
+ if( g_debugAlloc.integer )
+ G_Printf( "G_Alloc of %i bytes (%i left)\n", allocsize, freemem );
+ memset( ptr, 0, allocsize );
+ *ptr++ = allocsize; // Store a copy of size for deallocation
+ return( (void *) ptr );
+ }
+
+ G_Error( "G_Alloc: failed on allocation of %i bytes\n", size );
+ return( NULL );
+}
+
+void G_Free( void *ptr )
+{
+ // Release allocated memory, add it to the free list.
+
+ struct freememnode *fmn;
+ char *freeend;
+ int *freeptr;
+
+ freeptr = ptr;
+ freeptr--;
+
+ freemem += *freeptr;
+ if( g_debugAlloc.integer )
+ G_Printf( "G_Free of %i bytes (%i left)\n", *freeptr, freemem );
+
+ for( fmn = freehead; fmn; fmn = fmn->next )
+ {
+ freeend = ((char *) fmn) + fmn->size;
+ if( freeend == (char *) freeptr )
+ {
+ // Released block can be merged to an existing node
+
+ fmn->size += *freeptr; // Add size of node.
+ return;
+ }
+ }
+ // No merging, add to head of list
+
+ fmn = (struct freememnode *) freeptr;
+ fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr
+ fmn->cookie = FREEMEMCOOKIE;
+ fmn->prev = NULL;
+ fmn->next = freehead;
+ freehead->prev = fmn;
+ freehead = fmn;
+}
+
+void G_InitMemory( void )
+{
+ // Set up the initial node
+
+ freehead = (struct freememnode *)memoryPool;
+ freehead->cookie = FREEMEMCOOKIE;
+ freehead->size = POOLSIZE;
+ freehead->next = NULL;
+ freehead->prev = NULL;
+ freemem = sizeof( memoryPool );
+}
+
+void G_DefragmentMemory( void )
+{
+ // If there's a frenzy of deallocation and we want to
+ // allocate something big, this is useful. Otherwise...
+ // not much use.
+
+ struct freememnode *startfmn, *endfmn, *fmn;
+
+ for( startfmn = freehead; startfmn; )
+ {
+ endfmn = (struct freememnode *)(((char *) startfmn) + startfmn->size);
+ for( fmn = freehead; fmn; )
+ {
+ if( fmn->cookie != FREEMEMCOOKIE )
+ G_Error( "G_DefragmentMemory: Memory corruption detected!\n" );
+
+ if( fmn == endfmn )
+ {
+ // We can add fmn onto startfmn.
+
+ if( fmn->prev )
+ fmn->prev->next = fmn->next;
+ if( fmn->next )
+ {
+ if( !(fmn->next->prev = fmn->prev) )
+ freehead = fmn->next; // We're removing the head node
+ }
+ startfmn->size += fmn->size;
+ memset( fmn, 0, sizeof(struct freememnode) ); // A redundant call, really.
+
+ startfmn = freehead;
+ endfmn = fmn = NULL; // Break out of current loop
+ }
+ else
+ fmn = fmn->next;
+ }
+
+ if( endfmn )
+ startfmn = startfmn->next; // endfmn acts as a 'restart' flag here
+ }
+}
+
+void Svcmd_GameMem_f( void )
+{
+ // Give a breakdown of memory
+
+ struct freememnode *fmn;
+
+ G_Printf( "Game memory status: %i out of %i bytes allocated\n", POOLSIZE - freemem, POOLSIZE );
+
+ for( fmn = freehead; fmn; fmn = fmn->next )
+ G_Printf( " %dd: %d bytes free.\n", fmn, fmn->size );
+ G_Printf( "Status complete.\n" );
+}
+
diff --git a/src/game/g_misc.c b/src/game/g_misc.c
new file mode 100644
index 0000000..a68eeb8
--- /dev/null
+++ b/src/game/g_misc.c
@@ -0,0 +1,436 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+
+/*QUAKED func_group (0 0 0) ?
+Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
+*/
+
+
+/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
+*/
+void SP_info_null( gentity_t *self )
+{
+ G_FreeEntity( self );
+}
+
+
+/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for in-game calculation, like jumppad targets.
+target_position does the same thing
+*/
+void SP_info_notnull( gentity_t *self )
+{
+ G_SetOrigin( self, self->s.origin );
+}
+
+
+/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear
+Non-displayed light.
+"light" overrides the default 300 intensity.
+Linear checbox gives linear falloff instead of inverse square
+Lights pointed at a target will be spotlights.
+"radius" overrides the default 64 unit radius of a spotlight at the target point.
+*/
+void SP_light( gentity_t *self )
+{
+ G_FreeEntity( self );
+}
+
+
+
+/*
+=================================================================================
+
+TELEPORTERS
+
+=================================================================================
+*/
+
+void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles )
+{
+ // unlink to make sure it can't possibly interfere with G_KillBox
+ trap_UnlinkEntity( player );
+
+ VectorCopy( origin, player->client->ps.origin );
+ player->client->ps.origin[ 2 ] += 1;
+
+ // spit the player out
+ AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
+ VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
+ player->client->ps.pm_time = 160; // hold time
+ player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
+
+ // toggle the teleport bit so the client knows to not lerp
+ player->client->ps.eFlags ^= EF_TELEPORT_BIT;
+ G_UnlaggedClear( player );
+
+ // set angles
+ G_SetClientViewAngle( player, angles );
+
+ // kill anything at the destination
+ if( player->client->sess.sessionTeam != TEAM_SPECTATOR )
+ G_KillBox( player );
+
+ // save results of pmove
+ BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
+
+ // use the precise origin for linking
+ VectorCopy( player->client->ps.origin, player->r.currentOrigin );
+
+ if( player->client->sess.sessionTeam != TEAM_SPECTATOR )
+ trap_LinkEntity (player);
+}
+
+
+/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
+Point teleporters at these.
+Now that we don't have teleport destination pads, this is just
+an info_notnull
+*/
+void SP_misc_teleporter_dest( gentity_t *ent )
+{
+}
+
+
+//===========================================================
+
+/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
+"model" arbitrary .md3 file to display
+*/
+void SP_misc_model( gentity_t *ent )
+{
+#if 0
+ ent->s.modelindex = G_ModelIndex( ent->model );
+ VectorSet (ent->mins, -16, -16, -16);
+ VectorSet (ent->maxs, 16, 16, 16);
+ trap_LinkEntity (ent);
+
+ G_SetOrigin( ent, ent->s.origin );
+ VectorCopy( ent->s.angles, ent->s.apos.trBase );
+#else
+ G_FreeEntity( ent );
+#endif
+}
+
+//===========================================================
+
+void locateCamera( gentity_t *ent )
+{
+ vec3_t dir;
+ gentity_t *target;
+ gentity_t *owner;
+
+ owner = G_PickTarget( ent->target );
+ if( !owner )
+ {
+ G_Printf( "Couldn't find target for misc_portal_surface\n" );
+ G_FreeEntity( ent );
+ return;
+ }
+ ent->r.ownerNum = owner->s.number;
+
+ // frame holds the rotate speed
+ if( owner->spawnflags & 1 )
+ ent->s.frame = 25;
+ else if( owner->spawnflags & 2 )
+ ent->s.frame = 75;
+
+ // swing camera ?
+ if( owner->spawnflags & 4 )
+ {
+ // set to 0 for no rotation at all
+ ent->s.misc = 0;
+ }
+ else
+ ent->s.misc = 1;
+
+ // clientNum holds the rotate offset
+ ent->s.clientNum = owner->s.clientNum;
+
+ VectorCopy( owner->s.origin, ent->s.origin2 );
+
+ // see if the portal_camera has a target
+ target = G_PickTarget( owner->target );
+ if( target )
+ {
+ VectorSubtract( target->s.origin, owner->s.origin, dir );
+ VectorNormalize( dir );
+ }
+ else
+ G_SetMovedir( owner->s.angles, dir );
+
+ ent->s.eventParm = DirToByte( dir );
+}
+
+/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
+The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
+This must be within 64 world units of the surface!
+*/
+void SP_misc_portal_surface( gentity_t *ent )
+{
+ VectorClear( ent->r.mins );
+ VectorClear( ent->r.maxs );
+ trap_LinkEntity( ent );
+
+ ent->r.svFlags = SVF_PORTAL;
+ ent->s.eType = ET_PORTAL;
+
+ if( !ent->target )
+ {
+ VectorCopy( ent->s.origin, ent->s.origin2 );
+ }
+ else
+ {
+ ent->think = locateCamera;
+ ent->nextthink = level.time + 100;
+ }
+}
+
+/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing
+
+The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view.
+"roll" an angle modifier to orient the camera around the target vector;
+*/
+void SP_misc_portal_camera( gentity_t *ent )
+{
+ float roll;
+
+ VectorClear( ent->r.mins );
+ VectorClear( ent->r.maxs );
+ trap_LinkEntity( ent );
+
+ G_SpawnFloat( "roll", "0", &roll );
+
+ ent->s.clientNum = roll / 360.0f * 256;
+}
+
+/*
+======================================================================
+
+ NEAT EFFECTS AND STUFF FOR TREMULOUS
+
+======================================================================
+*/
+
+void SP_toggle_particle_system( gentity_t *self )
+{
+ //toggle EF_NODRAW
+ self->s.eFlags ^= EF_NODRAW;
+
+ self->nextthink = 0;
+}
+
+/*
+===============
+SP_use_particle_system
+
+Use function for particle_system
+===============
+*/
+void SP_use_particle_system( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ SP_toggle_particle_system( self );
+
+ if( self->wait > 0.0f )
+ {
+ self->think = SP_toggle_particle_system;
+ self->nextthink = level.time + (int)( self->wait * 1000 );
+ }
+}
+
+/*
+===============
+SP_spawn_particle_system
+
+Spawn function for particle system
+===============
+*/
+void SP_misc_particle_system( gentity_t *self )
+{
+ char *s;
+
+ G_SetOrigin( self, self->s.origin );
+
+ G_SpawnString( "psName", "", &s );
+ G_SpawnFloat( "wait", "0", &self->wait );
+
+ //add the particle system to the client precache list
+ self->s.modelindex = G_ParticleSystemIndex( s );
+
+ if( self->spawnflags & 1 )
+ self->s.eFlags |= EF_NODRAW;
+
+ self->use = SP_use_particle_system;
+ self->s.eType = ET_PARTICLE_SYSTEM;
+ trap_LinkEntity( self );
+}
+
+/*
+===============
+SP_use_anim_model
+
+Use function for anim model
+===============
+*/
+void SP_use_anim_model( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( self->spawnflags & 1 )
+ {
+ //if spawnflag 1 is set
+ //toggle EF_NODRAW
+ if( self->s.eFlags & EF_NODRAW )
+ self->s.eFlags &= ~EF_NODRAW;
+ else
+ self->s.eFlags |= EF_NODRAW;
+ }
+ else
+ {
+ //if the animation loops then toggle the animation
+ //toggle EF_MOVER_STOP
+ if( self->s.eFlags & EF_MOVER_STOP )
+ self->s.eFlags &= ~EF_MOVER_STOP;
+ else
+ self->s.eFlags |= EF_MOVER_STOP;
+ }
+}
+
+/*
+===============
+SP_misc_anim_model
+
+Spawn function for anim model
+===============
+*/
+void SP_misc_anim_model( gentity_t *self )
+{
+ self->s.misc = (int)self->animation[ 0 ];
+ self->s.weapon = (int)self->animation[ 1 ];
+ self->s.torsoAnim = (int)self->animation[ 2 ];
+ self->s.legsAnim = (int)self->animation[ 3 ];
+
+ self->s.angles2[ 0 ] = self->pos2[ 0 ];
+
+ //add the model to the client precache list
+ self->s.modelindex = G_ModelIndex( self->model );
+
+ self->use = SP_use_anim_model;
+
+ self->s.eType = ET_ANIMMAPOBJ;
+
+ // spawn with animation stopped
+ if( self->spawnflags & 2 )
+ self->s.eFlags |= EF_MOVER_STOP;
+
+ trap_LinkEntity( self );
+}
+
+/*
+===============
+SP_use_light_flare
+
+Use function for light flare
+===============
+*/
+void SP_use_light_flare( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ self->s.eFlags ^= EF_NODRAW;
+}
+
+/*
+===============
+findEmptySpot
+
+Finds an empty spot radius units from origin
+==============
+*/
+static void findEmptySpot( vec3_t origin, float radius, vec3_t spot )
+{
+ int i, j, k;
+ vec3_t delta, test, total;
+ trace_t tr;
+
+ VectorClear( total );
+
+ //54(!) traces to test for empty spots
+ for( i = -1; i <= 1; i++ )
+ {
+ for( j = -1; j <= 1; j++ )
+ {
+ for( k = -1; k <= 1; k++ )
+ {
+ VectorSet( delta, ( i * radius ),
+ ( j * radius ),
+ ( k * radius ) );
+
+ VectorAdd( origin, delta, test );
+
+ trap_Trace( &tr, test, NULL, NULL, test, -1, MASK_SOLID );
+
+ if( !tr.allsolid )
+ {
+ trap_Trace( &tr, test, NULL, NULL, origin, -1, MASK_SOLID );
+ VectorScale( delta, tr.fraction, delta );
+ VectorAdd( total, delta, total );
+ }
+ }
+ }
+ }
+
+ VectorNormalize( total );
+ VectorScale( total, radius, total );
+ VectorAdd( origin, total, spot );
+}
+
+/*
+===============
+SP_misc_light_flare
+
+Spawn function for light flare
+===============
+*/
+void SP_misc_light_flare( gentity_t *self )
+{
+ self->s.eType = ET_LIGHTFLARE;
+ self->s.modelindex = G_ShaderIndex( self->targetShaderName );
+ VectorCopy( self->pos2, self->s.origin2 );
+
+ //try to find a spot near to the flare which is empty. This
+ //is used to facilitate visibility testing
+ findEmptySpot( self->s.origin, 8.0f, self->s.angles2 );
+
+ self->use = SP_use_light_flare;
+
+ G_SpawnFloat( "speed", "200", &self->speed );
+ self->s.time = self->speed;
+
+ G_SpawnInt( "mindist", "0", &self->s.generic1 );
+
+ if( self->spawnflags & 1 )
+ self->s.eFlags |= EF_NODRAW;
+
+ trap_LinkEntity( self );
+}
diff --git a/src/game/g_missile.c b/src/game/g_missile.c
new file mode 100644
index 0000000..6317636
--- /dev/null
+++ b/src/game/g_missile.c
@@ -0,0 +1,810 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+#define MISSILE_PRESTEP_TIME 50
+
+/*
+================
+G_BounceMissile
+
+================
+*/
+void G_BounceMissile( gentity_t *ent, trace_t *trace )
+{
+ vec3_t velocity;
+ float dot;
+ int hitTime;
+
+ // reflect the velocity on the trace plane
+ hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
+ BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
+ dot = DotProduct( velocity, trace->plane.normal );
+ VectorMA( velocity, -2 * dot, trace->plane.normal, ent->s.pos.trDelta );
+
+ if( ent->s.eFlags & EF_BOUNCE_HALF )
+ {
+ VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta );
+ // check for stop
+ if( trace->plane.normal[ 2 ] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 )
+ {
+ G_SetOrigin( ent, trace->endpos );
+ return;
+ }
+ }
+
+ VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin );
+ VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
+ ent->s.pos.trTime = level.time;
+}
+
+
+/*
+================
+G_ExplodeMissile
+
+Explode a missile without an impact
+================
+*/
+void G_ExplodeMissile( gentity_t *ent )
+{
+ vec3_t dir;
+ vec3_t origin;
+
+ BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
+ SnapVector( origin );
+ G_SetOrigin( ent, origin );
+
+ // we don't have a valid direction, so just point straight up
+ dir[ 0 ] = dir[ 1 ] = 0;
+ dir[ 2 ] = 1;
+
+ ent->s.eType = ET_GENERAL;
+
+ //TA: tired... can't be fucked... hack
+ if( ent->s.weapon != WP_LOCKBLOB_LAUNCHER &&
+ ent->s.weapon != WP_FLAMER )
+ G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
+
+ ent->freeAfterEvent = qtrue;
+
+ // splash damage
+ if( ent->splashDamage )
+ G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage,
+ ent->splashRadius, ent, ent->splashMethodOfDeath );
+
+ trap_LinkEntity( ent );
+}
+
+void AHive_ReturnToHive( gentity_t *self );
+
+/*
+================
+G_MissileImpact
+
+================
+*/
+void G_MissileImpact( gentity_t *ent, trace_t *trace )
+{
+ gentity_t *other, *attacker;
+ qboolean returnAfterDamage = qfalse;
+ vec3_t dir;
+
+ other = &g_entities[ trace->entityNum ];
+ attacker = &g_entities[ ent->r.ownerNum ];
+
+ // check for bounce
+ if( !other->takedamage &&
+ ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
+ {
+ G_BounceMissile( ent, trace );
+
+ //only play a sound if requested
+ if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) )
+ G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
+
+ return;
+ }
+
+ if( !strcmp( ent->classname, "grenade" ) )
+ {
+ //grenade doesn't explode on impact
+ G_BounceMissile( ent, trace );
+
+ //only play a sound if requested
+ if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) )
+ G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
+
+ return;
+ }
+ else if( !strcmp( ent->classname, "lockblob" ) )
+ {
+ if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ other->client->ps.stats[ STAT_STATE ] |= SS_BLOBLOCKED;
+ other->client->lastLockTime = level.time;
+ AngleVectors( other->client->ps.viewangles, dir, NULL, NULL );
+ other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir );
+ }
+ }
+ else if( !strcmp( ent->classname, "slowblob" ) )
+ {
+ if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ other->client->ps.stats[ STAT_STATE ] |= SS_SLOWLOCKED;
+ other->client->lastSlowTime = level.time;
+ AngleVectors( other->client->ps.viewangles, dir, NULL, NULL );
+ other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir );
+ }
+ }
+ else if( !strcmp( ent->classname, "hive" ) )
+ {
+ if( other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_HIVE )
+ {
+ if( !ent->parent )
+ G_Printf( S_COLOR_YELLOW "WARNING: hive entity has no parent in G_MissileImpact\n" );
+ else
+ ent->parent->active = qfalse;
+
+ G_FreeEntity( ent );
+ return;
+ }
+ else
+ {
+ //prevent collision with the client when returning
+ ent->r.ownerNum = other->s.number;
+
+ ent->think = AHive_ReturnToHive;
+ ent->nextthink = level.time + FRAMETIME;
+
+ //only damage humans
+ if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ returnAfterDamage = qtrue;
+ else
+ return;
+ }
+ }
+
+ // impact damage
+ if( other->takedamage )
+ {
+ // FIXME: wrong damage direction?
+ if( ent->damage )
+ {
+ vec3_t velocity;
+
+ BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
+ if( VectorLength( velocity ) == 0 )
+ velocity[ 2 ] = 1; // stepped on a grenade
+
+ G_Damage( other, ent, attacker, velocity, ent->s.origin, ent->damage,
+ 0, ent->methodOfDeath );
+ }
+ }
+
+ if( returnAfterDamage )
+ return;
+
+ // is it cheaper in bandwidth to just remove this ent and create a new
+ // one, rather than changing the missile into the explosion?
+
+ if( other->takedamage && other->client )
+ {
+ G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
+ ent->s.otherEntityNum = other->s.number;
+ }
+ else if( trace->surfaceFlags & SURF_METALSTEPS )
+ G_AddEvent( ent, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) );
+ else
+ G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) );
+
+ ent->freeAfterEvent = qtrue;
+
+ // change over to a normal entity right at the point of impact
+ ent->s.eType = ET_GENERAL;
+
+ SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth
+
+ G_SetOrigin( ent, trace->endpos );
+
+ // splash damage (doesn't apply to person directly hit)
+ if( ent->splashDamage )
+ G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius,
+ other, ent->splashMethodOfDeath );
+
+ trap_LinkEntity( ent );
+}
+
+
+/*
+================
+G_RunMissile
+
+================
+*/
+void G_RunMissile( gentity_t *ent )
+{
+ vec3_t origin;
+ trace_t tr;
+ int passent;
+
+ // get current position
+ BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
+
+ // ignore interactions with the missile owner
+ passent = ent->r.ownerNum;
+
+ // trace a line from the previous position to the current position
+ trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask );
+
+ if( tr.startsolid || tr.allsolid )
+ {
+ // make sure the tr.entityNum is set to the entity we're stuck in
+ trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, ent->clipmask );
+ tr.fraction = 0;
+ }
+ else
+ VectorCopy( tr.endpos, ent->r.currentOrigin );
+
+ ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into...
+ trap_LinkEntity( ent );
+ ent->r.contents = 0; //...encoding bbox information
+
+ if( tr.fraction != 1 )
+ {
+ // never explode or bounce on sky
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ {
+ // If grapple, reset owner
+ if( ent->parent && ent->parent->client && ent->parent->client->hook == ent )
+ ent->parent->client->hook = NULL;
+
+ G_FreeEntity( ent );
+ return;
+ }
+
+ G_MissileImpact( ent, &tr );
+ if( ent->s.eType != ET_MISSILE )
+ return; // exploded
+ }
+
+ // check think function after bouncing
+ G_RunThink( ent );
+}
+
+
+//=============================================================================
+
+/*
+=================
+fire_flamer
+
+=================
+*/
+gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+ vec3_t pvel;
+
+ VectorNormalize (dir);
+
+ bolt = G_Spawn();
+ bolt->classname = "flame";
+ bolt->nextthink = level.time + FLAMER_LIFETIME;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_FLAMER;
+ bolt->s.generic1 = self->s.generic1; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = FLAMER_DMG;
+ bolt->splashDamage = FLAMER_DMG;
+ bolt->splashRadius = FLAMER_RADIUS;
+ bolt->methodOfDeath = MOD_FLAMER;
+ bolt->splashMethodOfDeath = MOD_FLAMER_SPLASH;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+ bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -15.0f;
+ bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 15.0f;
+
+ bolt->s.pos.trType = TR_LINEAR;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( self->client->ps.velocity, FLAMER_LAG, pvel );
+ VectorMA( pvel, FLAMER_SPEED, dir, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+//=============================================================================
+
+/*
+=================
+fire_blaster
+
+=================
+*/
+gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize (dir);
+
+ bolt = G_Spawn();
+ bolt->classname = "blaster";
+ bolt->nextthink = level.time + 10000;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_BLASTER;
+ bolt->s.generic1 = self->s.generic1; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = BLASTER_DMG;
+ bolt->splashDamage = 0;
+ bolt->splashRadius = 0;
+ bolt->methodOfDeath = MOD_BLASTER;
+ bolt->splashMethodOfDeath = MOD_BLASTER;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+
+ bolt->s.pos.trType = TR_LINEAR;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, BLASTER_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+//=============================================================================
+
+/*
+=================
+fire_pulseRifle
+
+=================
+*/
+gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize (dir);
+
+ bolt = G_Spawn();
+ bolt->classname = "pulse";
+ bolt->nextthink = level.time + 10000;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_PULSE_RIFLE;
+ bolt->s.generic1 = self->s.generic1; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = PRIFLE_DMG;
+ bolt->splashDamage = 0;
+ bolt->splashRadius = 0;
+ bolt->methodOfDeath = MOD_PRIFLE;
+ bolt->splashMethodOfDeath = MOD_PRIFLE;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+
+ bolt->s.pos.trType = TR_LINEAR;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, PRIFLE_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+//=============================================================================
+
+/*
+=================
+fire_luciferCannon
+
+=================
+*/
+gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius )
+{
+ gentity_t *bolt;
+ int localDamage = (int)( ceil( ( (float)damage /
+ (float)LCANNON_TOTAL_CHARGE ) * (float)LCANNON_DAMAGE ) );
+
+ VectorNormalize( dir );
+
+ bolt = G_Spawn( );
+ bolt->classname = "lcannon";
+
+ if( damage == LCANNON_TOTAL_CHARGE )
+ bolt->nextthink = level.time;
+ else
+ bolt->nextthink = level.time + 10000;
+
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_LUCIFER_CANNON;
+ bolt->s.generic1 = self->s.generic1; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = localDamage;
+ bolt->splashDamage = localDamage / 2;
+ bolt->splashRadius = radius;
+ bolt->methodOfDeath = MOD_LCANNON;
+ bolt->splashMethodOfDeath = MOD_LCANNON_SPLASH;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+
+ bolt->s.pos.trType = TR_LINEAR;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, LCANNON_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+/*
+=================
+launch_grenade
+
+=================
+*/
+gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize( dir );
+
+ bolt = G_Spawn( );
+ bolt->classname = "grenade";
+ bolt->nextthink = level.time + 5000;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_GRENADE;
+ bolt->s.eFlags = EF_BOUNCE_HALF;
+ bolt->s.generic1 = WPM_PRIMARY; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = GRENADE_DAMAGE;
+ bolt->splashDamage = GRENADE_DAMAGE;
+ bolt->splashRadius = GRENADE_RANGE;
+ bolt->methodOfDeath = MOD_GRENADE;
+ bolt->splashMethodOfDeath = MOD_GRENADE;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+ bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -3.0f;
+ bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 3.0f;
+ bolt->s.time = level.time;
+
+ bolt->s.pos.trType = TR_GRAVITY;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, GRENADE_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+//=============================================================================
+
+/*
+================
+AHive_ReturnToHive
+
+Adjust the trajectory to point towards the hive
+================
+*/
+void AHive_ReturnToHive( gentity_t *self )
+{
+ vec3_t dir;
+ trace_t tr;
+
+ if( !self->parent )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: AHive_ReturnToHive called with no self->parent\n" );
+ return;
+ }
+
+ trap_UnlinkEntity( self->parent );
+ trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
+ self->parent->r.currentOrigin, self->r.ownerNum, self->clipmask );
+ trap_LinkEntity( self->parent );
+
+ if( tr.fraction < 1.0f )
+ {
+ //if can't see hive then disperse
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
+ self->s.pos.trType = TR_STATIONARY;
+ self->s.pos.trTime = level.time;
+
+ self->think = G_ExplodeMissile;
+ self->nextthink = level.time + 2000;
+ self->parent->active = qfalse; //allow the parent to start again
+ }
+ else
+ {
+ VectorSubtract( self->parent->r.currentOrigin, self->r.currentOrigin, dir );
+ VectorNormalize( dir );
+
+ //change direction towards the hive
+ VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta );
+ SnapVector( self->s.pos.trDelta ); // save net bandwidth
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
+ self->s.pos.trTime = level.time;
+
+ self->think = G_ExplodeMissile;
+ self->nextthink = level.time + 15000;
+ }
+}
+
+/*
+================
+AHive_SearchAndDestroy
+
+Adjust the trajectory to point towards the target
+================
+*/
+void AHive_SearchAndDestroy( gentity_t *self )
+{
+ vec3_t dir;
+ trace_t tr;
+
+ trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
+ self->target_ent->r.currentOrigin, self->r.ownerNum, self->clipmask );
+
+ //if there is no LOS or the parent hive is too far away or the target is dead or notargeting, return
+ if( tr.entityNum == ENTITYNUM_WORLD ||
+ Distance( self->r.currentOrigin, self->parent->r.currentOrigin ) > ( HIVE_RANGE * 5 ) ||
+ self->target_ent->health <= 0 || self->target_ent->flags & FL_NOTARGET )
+ {
+ self->r.ownerNum = ENTITYNUM_WORLD;
+
+ self->think = AHive_ReturnToHive;
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ VectorSubtract( self->target_ent->r.currentOrigin, self->r.currentOrigin, dir );
+ VectorNormalize( dir );
+
+ //change direction towards the player
+ VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta );
+ SnapVector( self->s.pos.trDelta ); // save net bandwidth
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
+ self->s.pos.trTime = level.time;
+
+ self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;
+ }
+}
+
+/*
+=================
+fire_hive
+=================
+*/
+gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize ( dir );
+
+ bolt = G_Spawn( );
+ bolt->classname = "hive";
+ bolt->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;
+ bolt->think = AHive_SearchAndDestroy;
+ bolt->s.eType = ET_MISSILE;
+ bolt->s.eFlags |= EF_BOUNCE|EF_NO_BOUNCE_SOUND;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_HIVE;
+ bolt->s.generic1 = WPM_PRIMARY; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = HIVE_DMG;
+ bolt->splashDamage = 0;
+ bolt->splashRadius = 0;
+ bolt->methodOfDeath = MOD_SWARM;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = self->target_ent;
+
+ bolt->s.pos.trType = TR_LINEAR;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, HIVE_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+//=============================================================================
+
+/*
+=================
+fire_lockblob
+=================
+*/
+gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize ( dir );
+
+ bolt = G_Spawn( );
+ bolt->classname = "lockblob";
+ bolt->nextthink = level.time + 15000;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_LOCKBLOB_LAUNCHER;
+ bolt->s.generic1 = WPM_PRIMARY; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = 0;
+ bolt->splashDamage = 0;
+ bolt->splashRadius = 0;
+ bolt->methodOfDeath = MOD_UNKNOWN; //doesn't do damage so will never kill
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+
+ bolt->s.pos.trType = TR_LINEAR;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, 500, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+/*
+=================
+fire_slowBlob
+=================
+*/
+gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize ( dir );
+
+ bolt = G_Spawn( );
+ bolt->classname = "slowblob";
+ bolt->nextthink = level.time + 15000;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_ABUILD2;
+ bolt->s.generic1 = self->s.generic1; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = ABUILDER_BLOB_DMG;
+ bolt->splashDamage = 0;
+ bolt->splashRadius = 0;
+ bolt->methodOfDeath = MOD_SLOWBLOB;
+ bolt->splashMethodOfDeath = MOD_SLOWBLOB;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+
+ bolt->s.pos.trType = TR_GRAVITY;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, ABUILDER_BLOB_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+/*
+=================
+fire_paraLockBlob
+=================
+*/
+gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize ( dir );
+
+ bolt = G_Spawn( );
+ bolt->classname = "lockblob";
+ bolt->nextthink = level.time + 15000;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_LOCKBLOB_LAUNCHER;
+ bolt->s.generic1 = self->s.generic1; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = 0;
+ bolt->splashDamage = 0;
+ bolt->splashRadius = 0;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+
+ bolt->s.pos.trType = TR_GRAVITY;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, LOCKBLOB_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+ VectorCopy( start, bolt->r.currentOrigin );
+
+ return bolt;
+}
+
+/*
+=================
+fire_bounceBall
+=================
+*/
+gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir )
+{
+ gentity_t *bolt;
+
+ VectorNormalize ( dir );
+
+ bolt = G_Spawn( );
+ bolt->classname = "bounceball";
+ bolt->nextthink = level.time + 3000;
+ bolt->think = G_ExplodeMissile;
+ bolt->s.eType = ET_MISSILE;
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ bolt->s.weapon = WP_ALEVEL3_UPG;
+ bolt->s.generic1 = self->s.generic1; //weaponMode
+ bolt->r.ownerNum = self->s.number;
+ bolt->parent = self;
+ bolt->damage = LEVEL3_BOUNCEBALL_DMG;
+ bolt->splashDamage = 0;
+ bolt->splashRadius = 0;
+ bolt->methodOfDeath = MOD_LEVEL3_BOUNCEBALL;
+ bolt->splashMethodOfDeath = MOD_LEVEL3_BOUNCEBALL;
+ bolt->clipmask = MASK_SHOT;
+ bolt->target_ent = NULL;
+
+ bolt->s.pos.trType = TR_GRAVITY;
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
+ VectorCopy( start, bolt->s.pos.trBase );
+ VectorScale( dir, LEVEL3_BOUNCEBALL_SPEED, bolt->s.pos.trDelta );
+ SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
+ VectorCopy( start, bolt->r.currentOrigin );
+ /*bolt->s.eFlags |= EF_BOUNCE;*/
+
+ return bolt;
+}
+
diff --git a/src/game/g_mover.c b/src/game/g_mover.c
new file mode 100644
index 0000000..20b5c08
--- /dev/null
+++ b/src/game/g_mover.c
@@ -0,0 +1,2479 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+
+
+/*
+===============================================================================
+
+PUSHMOVE
+
+===============================================================================
+*/
+
+void MatchTeam( gentity_t *teamLeader, int moverState, int time );
+
+typedef struct
+{
+ gentity_t *ent;
+ vec3_t origin;
+ vec3_t angles;
+ float deltayaw;
+} pushed_t;
+
+pushed_t pushed[ MAX_GENTITIES ], *pushed_p;
+
+
+/*
+============
+G_TestEntityPosition
+
+============
+*/
+gentity_t *G_TestEntityPosition( gentity_t *ent )
+{
+ trace_t tr;
+ int mask;
+
+ if( ent->clipmask )
+ mask = ent->clipmask;
+ else
+ mask = MASK_SOLID;
+
+ if( ent->client )
+ trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask );
+ else
+ trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask );
+
+ if( tr.startsolid )
+ return &g_entities[ tr.entityNum ];
+
+ return NULL;
+}
+
+/*
+================
+G_CreateRotationMatrix
+================
+*/
+void G_CreateRotationMatrix( vec3_t angles, vec3_t matrix[ 3 ] )
+{
+ AngleVectors( angles, matrix[ 0 ], matrix[ 1 ], matrix[ 2 ] );
+ VectorInverse( matrix[ 1 ] );
+}
+
+/*
+================
+G_TransposeMatrix
+================
+*/
+void G_TransposeMatrix( vec3_t matrix[ 3 ], vec3_t transpose[ 3 ] )
+{
+ int i, j;
+
+ for( i = 0; i < 3; i++ )
+ {
+ for( j = 0; j < 3; j++ )
+ {
+ transpose[ i ][ j ] = matrix[ j ][ i ];
+ }
+ }
+}
+
+/*
+================
+G_RotatePoint
+================
+*/
+void G_RotatePoint( vec3_t point, vec3_t matrix[ 3 ] )
+{
+ vec3_t tvec;
+
+ VectorCopy( point, tvec );
+ point[ 0 ] = DotProduct( matrix[ 0 ], tvec );
+ point[ 1 ] = DotProduct( matrix[ 1 ], tvec );
+ point[ 2 ] = DotProduct( matrix[ 2 ], tvec );
+}
+
+/*
+==================
+G_TryPushingEntity
+
+Returns qfalse if the move is blocked
+==================
+*/
+qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove )
+{
+ vec3_t matrix[ 3 ], transpose[ 3 ];
+ vec3_t org, org2, move2;
+ gentity_t *block;
+
+ // EF_MOVER_STOP will just stop when contacting another entity
+ // instead of pushing it, but entities can still ride on top of it
+ if( ( pusher->s.eFlags & EF_MOVER_STOP ) &&
+ check->s.groundEntityNum != pusher->s.number )
+ return qfalse;
+
+ //don't try to move buildables unless standing on a mover
+ if( check->s.eType == ET_BUILDABLE &&
+ check->s.groundEntityNum != pusher->s.number )
+ return qfalse;
+
+ // save off the old position
+ if( pushed_p > &pushed[ MAX_GENTITIES ] )
+ G_Error( "pushed_p > &pushed[MAX_GENTITIES]" );
+
+ pushed_p->ent = check;
+ VectorCopy( check->s.pos.trBase, pushed_p->origin );
+ VectorCopy( check->s.apos.trBase, pushed_p->angles );
+
+ if( check->client )
+ {
+ pushed_p->deltayaw = check->client->ps.delta_angles[ YAW ];
+ VectorCopy( check->client->ps.origin, pushed_p->origin );
+ }
+ pushed_p++;
+
+ // try moving the contacted entity
+ // figure movement due to the pusher's amove
+ G_CreateRotationMatrix( amove, transpose );
+ G_TransposeMatrix( transpose, matrix );
+
+ if( check->client )
+ VectorSubtract( check->client->ps.origin, pusher->r.currentOrigin, org );
+ else
+ VectorSubtract( check->s.pos.trBase, pusher->r.currentOrigin, org );
+
+ VectorCopy( org, org2 );
+ G_RotatePoint( org2, matrix );
+ VectorSubtract( org2, org, move2 );
+ // add movement
+ VectorAdd( check->s.pos.trBase, move, check->s.pos.trBase );
+ VectorAdd( check->s.pos.trBase, move2, check->s.pos.trBase );
+
+ if( check->client )
+ {
+ VectorAdd( check->client->ps.origin, move, check->client->ps.origin );
+ VectorAdd( check->client->ps.origin, move2, check->client->ps.origin );
+ // make sure the client's view rotates when on a rotating mover
+ check->client->ps.delta_angles[ YAW ] += ANGLE2SHORT( amove[ YAW ] );
+ }
+
+ // may have pushed them off an edge
+ if( check->s.groundEntityNum != pusher->s.number )
+ check->s.groundEntityNum = -1;
+
+ block = G_TestEntityPosition( check );
+
+ if( !block )
+ {
+ // pushed ok
+ if( check->client )
+ VectorCopy( check->client->ps.origin, check->r.currentOrigin );
+ else
+ VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
+
+ trap_LinkEntity( check );
+ return qtrue;
+ }
+
+ // if it is ok to leave in the old position, do it
+ // this is only relevent for riding entities, not pushed
+ // Sliding trapdoors can cause this.
+ VectorCopy( ( pushed_p - 1 )->origin, check->s.pos.trBase );
+
+ if( check->client )
+ VectorCopy( ( pushed_p - 1 )->origin, check->client->ps.origin );
+
+ VectorCopy( ( pushed_p - 1 )->angles, check->s.apos.trBase );
+ block = G_TestEntityPosition( check );
+
+ if( !block )
+ {
+ check->s.groundEntityNum = -1;
+ pushed_p--;
+ return qtrue;
+ }
+
+ // blocked
+ return qfalse;
+}
+
+
+/*
+============
+G_MoverPush
+
+Objects need to be moved back on a failed push,
+otherwise riders would continue to slide.
+If qfalse is returned, *obstacle will be the blocking entity
+============
+*/
+qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle )
+{
+ int i, e;
+ gentity_t *check;
+ vec3_t mins, maxs;
+ pushed_t *p;
+ int entityList[ MAX_GENTITIES ];
+ int listedEntities;
+ vec3_t totalMins, totalMaxs;
+
+ *obstacle = NULL;
+
+
+ // mins/maxs are the bounds at the destination
+ // totalMins / totalMaxs are the bounds for the entire move
+ if( pusher->r.currentAngles[ 0 ] || pusher->r.currentAngles[ 1 ] || pusher->r.currentAngles[ 2 ]
+ || amove[ 0 ] || amove[ 1 ] || amove[ 2 ] )
+ {
+ float radius;
+
+ radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs );
+
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ mins[ i ] = pusher->r.currentOrigin[ i ] + move[ i ] - radius;
+ maxs[ i ] = pusher->r.currentOrigin[ i ] + move[ i ] + radius;
+ totalMins[ i ] = mins[ i ] - move[ i ];
+ totalMaxs[ i ] = maxs[ i ] - move[ i ];
+ }
+ }
+ else
+ {
+ for( i = 0; i < 3; i++ )
+ {
+ mins[ i ] = pusher->r.absmin[ i ] + move[ i ];
+ maxs[ i ] = pusher->r.absmax[ i ] + move[ i ];
+ }
+
+ VectorCopy( pusher->r.absmin, totalMins );
+ VectorCopy( pusher->r.absmax, totalMaxs );
+ for( i = 0; i < 3; i++ )
+ {
+ if( move[ i ] > 0 )
+ totalMaxs[ i ] += move[ i ];
+ else
+ totalMins[ i ] += move[ i ];
+ }
+ }
+
+ // unlink the pusher so we don't get it in the entityList
+ trap_UnlinkEntity( pusher );
+
+ listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
+
+ // move the pusher to it's final position
+ VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );
+ VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );
+ trap_LinkEntity( pusher );
+
+ // see if any solid entities are inside the final position
+ for( e = 0 ; e < listedEntities ; e++ )
+ {
+ check = &g_entities[ entityList[ e ] ];
+
+ // only push items and players
+ if( check->s.eType != ET_ITEM && check->s.eType != ET_BUILDABLE &&
+ check->s.eType != ET_CORPSE && check->s.eType != ET_PLAYER &&
+ !check->physicsObject )
+ continue;
+
+ // if the entity is standing on the pusher, it will definitely be moved
+ if( check->s.groundEntityNum != pusher->s.number )
+ {
+ // see if the ent needs to be tested
+ if( check->r.absmin[ 0 ] >= maxs[ 0 ]
+ || check->r.absmin[ 1 ] >= maxs[ 1 ]
+ || check->r.absmin[ 2 ] >= maxs[ 2 ]
+ || check->r.absmax[ 0 ] <= mins[ 0 ]
+ || check->r.absmax[ 1 ] <= mins[ 1 ]
+ || check->r.absmax[ 2 ] <= mins[ 2 ] )
+ continue;
+
+ // see if the ent's bbox is inside the pusher's final position
+ // this does allow a fast moving object to pass through a thin entity...
+ if( !G_TestEntityPosition( check ) )
+ continue;
+ }
+
+ // the entity needs to be pushed
+ if( G_TryPushingEntity( check, pusher, move, amove ) )
+ continue;
+
+ // the move was blocked an entity
+
+ // bobbing entities are instant-kill and never get blocked
+ if( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE )
+ {
+ G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
+ continue;
+ }
+
+
+ // save off the obstacle so we can call the block function (crush, etc)
+ *obstacle = check;
+
+ // move back any entities we already moved
+ // go backwards, so if the same entity was pushed
+ // twice, it goes back to the original position
+ for( p = pushed_p - 1; p >= pushed; p-- )
+ {
+ VectorCopy( p->origin, p->ent->s.pos.trBase );
+ VectorCopy( p->angles, p->ent->s.apos.trBase );
+
+ if( p->ent->client )
+ {
+ p->ent->client->ps.delta_angles[ YAW ] = p->deltayaw;
+ VectorCopy( p->origin, p->ent->client->ps.origin );
+ }
+
+ trap_LinkEntity( p->ent );
+ }
+
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+/*
+=================
+G_MoverTeam
+=================
+*/
+void G_MoverTeam( gentity_t *ent )
+{
+ vec3_t move, amove;
+ gentity_t *part, *obstacle;
+ vec3_t origin, angles;
+
+ obstacle = NULL;
+
+ // make sure all team slaves can move before commiting
+ // any moves or calling any think functions
+ // if the move is blocked, all moved objects will be backed out
+ pushed_p = pushed;
+ for( part = ent; part; part = part->teamchain )
+ {
+ // get current position
+ BG_EvaluateTrajectory( &part->s.pos, level.time, origin );
+ BG_EvaluateTrajectory( &part->s.apos, level.time, angles );
+ VectorSubtract( origin, part->r.currentOrigin, move );
+ VectorSubtract( angles, part->r.currentAngles, amove );
+ if( !G_MoverPush( part, move, amove, &obstacle ) )
+ break; // move was blocked
+ }
+
+ if( part )
+ {
+ // go back to the previous position
+ for( part = ent; part; part = part->teamchain )
+ {
+ part->s.pos.trTime += level.time - level.previousTime;
+ part->s.apos.trTime += level.time - level.previousTime;
+ BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin );
+ BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles );
+ trap_LinkEntity( part );
+ }
+
+ // if the pusher has a "blocked" function, call it
+ if( ent->blocked )
+ ent->blocked( ent, obstacle );
+
+ return;
+ }
+
+ // the move succeeded
+ for( part = ent; part; part = part->teamchain )
+ {
+ // call the reached function if time is at or past end point
+ if( part->s.pos.trType == TR_LINEAR_STOP )
+ {
+ if( level.time >= part->s.pos.trTime + part->s.pos.trDuration )
+ {
+ if( part->reached )
+ part->reached( part );
+ }
+ }
+ if ( part->s.apos.trType == TR_LINEAR_STOP ) {
+ if ( level.time >= part->s.apos.trTime + part->s.apos.trDuration ) {
+ if ( part->reached ) {
+ part->reached( part );
+ }
+ }
+ }
+ }
+}
+
+/*
+================
+G_RunMover
+
+================
+*/
+void G_RunMover( gentity_t *ent )
+{
+ // if not a team captain, don't do anything, because
+ // the captain will handle everything
+ if( ent->flags & FL_TEAMSLAVE )
+ return;
+
+ // if stationary at one of the positions, don't move anything
+ if( ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) &&
+ ent->moverState < MODEL_POS1 ) //yuck yuck hack
+ G_MoverTeam( ent );
+
+ // check think function
+ G_RunThink( ent );
+}
+
+/*
+============================================================================
+
+GENERAL MOVERS
+
+Doors, plats, and buttons are all binary (two position) movers
+Pos1 is "at rest", pos2 is "activated"
+============================================================================
+*/
+
+/*
+===============
+SetMoverState
+===============
+*/
+void SetMoverState( gentity_t *ent, moverState_t moverState, int time )
+{
+ vec3_t delta;
+ float f;
+
+ ent->moverState = moverState;
+
+ ent->s.pos.trTime = time;
+ ent->s.apos.trTime = time;
+
+ switch( moverState )
+ {
+ case MOVER_POS1:
+ VectorCopy( ent->pos1, ent->s.pos.trBase );
+ ent->s.pos.trType = TR_STATIONARY;
+ break;
+
+ case MOVER_POS2:
+ VectorCopy( ent->pos2, ent->s.pos.trBase );
+ ent->s.pos.trType = TR_STATIONARY;
+ break;
+
+ case MOVER_1TO2:
+ VectorCopy( ent->pos1, ent->s.pos.trBase );
+ VectorSubtract( ent->pos2, ent->pos1, delta );
+ f = 1000.0 / ent->s.pos.trDuration;
+ VectorScale( delta, f, ent->s.pos.trDelta );
+ ent->s.pos.trType = TR_LINEAR_STOP;
+ break;
+
+ case MOVER_2TO1:
+ VectorCopy( ent->pos2, ent->s.pos.trBase );
+ VectorSubtract( ent->pos1, ent->pos2, delta );
+ f = 1000.0 / ent->s.pos.trDuration;
+ VectorScale( delta, f, ent->s.pos.trDelta );
+ ent->s.pos.trType = TR_LINEAR_STOP;
+ break;
+
+ case ROTATOR_POS1:
+ VectorCopy( ent->pos1, ent->s.apos.trBase );
+ ent->s.apos.trType = TR_STATIONARY;
+ break;
+
+ case ROTATOR_POS2:
+ VectorCopy( ent->pos2, ent->s.apos.trBase );
+ ent->s.apos.trType = TR_STATIONARY;
+ break;
+
+ case ROTATOR_1TO2:
+ VectorCopy( ent->pos1, ent->s.apos.trBase );
+ VectorSubtract( ent->pos2, ent->pos1, delta );
+ f = 1000.0 / ent->s.apos.trDuration;
+ VectorScale( delta, f, ent->s.apos.trDelta );
+ ent->s.apos.trType = TR_LINEAR_STOP;
+ break;
+
+ case ROTATOR_2TO1:
+ VectorCopy( ent->pos2, ent->s.apos.trBase );
+ VectorSubtract( ent->pos1, ent->pos2, delta );
+ f = 1000.0 / ent->s.apos.trDuration;
+ VectorScale( delta, f, ent->s.apos.trDelta );
+ ent->s.apos.trType = TR_LINEAR_STOP;
+ break;
+
+ case MODEL_POS1:
+ break;
+
+ case MODEL_POS2:
+ break;
+
+ default:
+ break;
+ }
+
+ if( moverState >= MOVER_POS1 && moverState <= MOVER_2TO1 )
+ BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
+
+ if( moverState >= ROTATOR_POS1 && moverState <= ROTATOR_2TO1 )
+ BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles );
+
+ trap_LinkEntity( ent );
+}
+
+/*
+================
+MatchTeam
+
+All entities in a mover team will move from pos1 to pos2
+in the same amount of time
+================
+*/
+void MatchTeam( gentity_t *teamLeader, int moverState, int time )
+{
+ gentity_t *slave;
+
+ for( slave = teamLeader; slave; slave = slave->teamchain )
+ SetMoverState( slave, moverState, time );
+}
+
+
+
+/*
+================
+ReturnToPos1
+================
+*/
+void ReturnToPos1( gentity_t *ent )
+{
+ MatchTeam( ent, MOVER_2TO1, level.time );
+
+ // looping sound
+ ent->s.loopSound = ent->soundLoop;
+
+ // starting sound
+ if( ent->sound2to1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
+}
+
+
+/*
+================
+ReturnToApos1
+================
+*/
+void ReturnToApos1( gentity_t *ent )
+{
+ MatchTeam( ent, ROTATOR_2TO1, level.time );
+
+ // looping sound
+ ent->s.loopSound = ent->soundLoop;
+
+ // starting sound
+ if( ent->sound2to1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
+}
+
+
+/*
+================
+Think_ClosedModelDoor
+================
+*/
+void Think_ClosedModelDoor( gentity_t *ent )
+{
+ // play sound
+ if( ent->soundPos1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
+
+ // close areaportals
+ if( ent->teammaster == ent || !ent->teammaster )
+ trap_AdjustAreaPortalState( ent, qfalse );
+
+ ent->moverState = MODEL_POS1;
+}
+
+
+/*
+================
+Think_CloseModelDoor
+================
+*/
+void Think_CloseModelDoor( gentity_t *ent )
+{
+ int entityList[ MAX_GENTITIES ];
+ int numEntities, i;
+ gentity_t *clipBrush = ent->clipBrush;
+ gentity_t *check;
+ qboolean canClose = qtrue;
+
+ numEntities = trap_EntitiesInBox( clipBrush->r.absmin, clipBrush->r.absmax, entityList, MAX_GENTITIES );
+
+ //set brush solid
+ trap_LinkEntity( ent->clipBrush );
+
+ //see if any solid entities are inside the door
+ for( i = 0; i < numEntities; i++ )
+ {
+ check = &g_entities[ entityList[ i ] ];
+
+ //only test items and players
+ if( check->s.eType != ET_ITEM && check->s.eType != ET_BUILDABLE &&
+ check->s.eType != ET_CORPSE && check->s.eType != ET_PLAYER &&
+ !check->physicsObject )
+ continue;
+
+ //test is this entity collides with this door
+ if( G_TestEntityPosition( check ) )
+ canClose = qfalse;
+ }
+
+ //something is blocking this door
+ if( !canClose )
+ {
+ //set brush non-solid
+ trap_UnlinkEntity( ent->clipBrush );
+
+ ent->nextthink = level.time + ent->wait;
+ return;
+ }
+
+ //toggle door state
+ ent->s.legsAnim = qfalse;
+
+ // play sound
+ if( ent->sound2to1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
+
+ ent->moverState = MODEL_2TO1;
+
+ ent->think = Think_ClosedModelDoor;
+ ent->nextthink = level.time + ent->speed;
+}
+
+
+/*
+================
+Think_OpenModelDoor
+================
+*/
+void Think_OpenModelDoor( gentity_t *ent )
+{
+ //set brush non-solid
+ trap_UnlinkEntity( ent->clipBrush );
+
+ // looping sound
+ ent->s.loopSound = ent->soundLoop;
+
+ // starting sound
+ if( ent->soundPos2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
+
+ ent->moverState = MODEL_POS2;
+
+ // return to pos1 after a delay
+ ent->think = Think_CloseModelDoor;
+ ent->nextthink = level.time + ent->wait;
+
+ // fire targets
+ if( !ent->activator )
+ ent->activator = ent;
+
+ G_UseTargets( ent, ent->activator );
+}
+
+
+/*
+================
+Reached_BinaryMover
+================
+*/
+void Reached_BinaryMover( gentity_t *ent )
+{
+ // stop the looping sound
+ ent->s.loopSound = ent->soundLoop;
+
+ if( ent->moverState == MOVER_1TO2 )
+ {
+ // reached pos2
+ SetMoverState( ent, MOVER_POS2, level.time );
+
+ // play sound
+ if( ent->soundPos2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
+
+ // return to pos1 after a delay
+ ent->think = ReturnToPos1;
+ ent->nextthink = level.time + ent->wait;
+
+ // fire targets
+ if( !ent->activator )
+ ent->activator = ent;
+
+ G_UseTargets( ent, ent->activator );
+ }
+ else if( ent->moverState == MOVER_2TO1 )
+ {
+ // reached pos1
+ SetMoverState( ent, MOVER_POS1, level.time );
+
+ // play sound
+ if( ent->soundPos1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
+
+ // close areaportals
+ if( ent->teammaster == ent || !ent->teammaster )
+ trap_AdjustAreaPortalState( ent, qfalse );
+ }
+ else if( ent->moverState == ROTATOR_1TO2 )
+ {
+ // reached pos2
+ SetMoverState( ent, ROTATOR_POS2, level.time );
+
+ // play sound
+ if( ent->soundPos2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
+
+ // return to apos1 after a delay
+ ent->think = ReturnToApos1;
+ ent->nextthink = level.time + ent->wait;
+
+ // fire targets
+ if( !ent->activator )
+ ent->activator = ent;
+
+ G_UseTargets( ent, ent->activator );
+ }
+ else if( ent->moverState == ROTATOR_2TO1 )
+ {
+ // reached pos1
+ SetMoverState( ent, ROTATOR_POS1, level.time );
+
+ // play sound
+ if( ent->soundPos1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
+
+ // close areaportals
+ if( ent->teammaster == ent || !ent->teammaster )
+ trap_AdjustAreaPortalState( ent, qfalse );
+ }
+ else
+ G_Error( "Reached_BinaryMover: bad moverState" );
+}
+
+
+/*
+================
+Use_BinaryMover
+================
+*/
+void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ int total;
+ int partial;
+
+ // if this is a non-client-usable door return
+ if( ent->targetname && other && other->client )
+ return;
+
+ // only the master should be used
+ if( ent->flags & FL_TEAMSLAVE )
+ {
+ Use_BinaryMover( ent->teammaster, other, activator );
+ return;
+ }
+
+ ent->activator = activator;
+
+ if( ent->moverState == MOVER_POS1 )
+ {
+ // start moving 50 msec later, becase if this was player
+ // triggered, level.time hasn't been advanced yet
+ MatchTeam( ent, MOVER_1TO2, level.time + 50 );
+
+ // starting sound
+ if( ent->sound1to2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
+
+ // looping sound
+ ent->s.loopSound = ent->soundLoop;
+
+ // open areaportal
+ if( ent->teammaster == ent || !ent->teammaster )
+ trap_AdjustAreaPortalState( ent, qtrue );
+ }
+ else if( ent->moverState == MOVER_POS2 )
+ {
+ // if all the way up, just delay before coming down
+ ent->nextthink = level.time + ent->wait;
+ }
+ else if( ent->moverState == MOVER_2TO1 )
+ {
+ // only partway down before reversing
+ total = ent->s.pos.trDuration;
+ partial = level.time - ent->s.pos.trTime;
+
+ if( partial > total )
+ partial = total;
+
+ MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) );
+
+ if( ent->sound1to2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
+ }
+ else if( ent->moverState == MOVER_1TO2 )
+ {
+ // only partway up before reversing
+ total = ent->s.pos.trDuration;
+ partial = level.time - ent->s.pos.trTime;
+
+ if( partial > total )
+ partial = total;
+
+ MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) );
+
+ if( ent->sound2to1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
+ }
+ else if( ent->moverState == ROTATOR_POS1 )
+ {
+ // start moving 50 msec later, becase if this was player
+ // triggered, level.time hasn't been advanced yet
+ MatchTeam( ent, ROTATOR_1TO2, level.time + 50 );
+
+ // starting sound
+ if( ent->sound1to2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
+
+ // looping sound
+ ent->s.loopSound = ent->soundLoop;
+
+ // open areaportal
+ if( ent->teammaster == ent || !ent->teammaster )
+ trap_AdjustAreaPortalState( ent, qtrue );
+ }
+ else if( ent->moverState == ROTATOR_POS2 )
+ {
+ // if all the way up, just delay before coming down
+ ent->nextthink = level.time + ent->wait;
+ }
+ else if( ent->moverState == ROTATOR_2TO1 )
+ {
+ // only partway down before reversing
+ total = ent->s.apos.trDuration;
+ partial = level.time - ent->s.apos.trTime;
+
+ if( partial > total )
+ partial = total;
+
+ MatchTeam( ent, ROTATOR_1TO2, level.time - ( total - partial ) );
+
+ if( ent->sound1to2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
+ }
+ else if( ent->moverState == ROTATOR_1TO2 )
+ {
+ // only partway up before reversing
+ total = ent->s.apos.trDuration;
+ partial = level.time - ent->s.apos.trTime;
+
+ if( partial > total )
+ partial = total;
+
+ MatchTeam( ent, ROTATOR_2TO1, level.time - ( total - partial ) );
+
+ if( ent->sound2to1 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
+ }
+ else if( ent->moverState == MODEL_POS1 )
+ {
+ //toggle door state
+ ent->s.legsAnim = qtrue;
+
+ ent->think = Think_OpenModelDoor;
+ ent->nextthink = level.time + ent->speed;
+
+ // starting sound
+ if( ent->sound1to2 )
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
+
+ // looping sound
+ ent->s.loopSound = ent->soundLoop;
+
+ // open areaportal
+ if( ent->teammaster == ent || !ent->teammaster )
+ trap_AdjustAreaPortalState( ent, qtrue );
+
+ ent->moverState = MODEL_1TO2;
+ }
+ else if( ent->moverState == MODEL_POS2 )
+ {
+ // if all the way up, just delay before coming down
+ ent->nextthink = level.time + ent->wait;
+ }
+}
+
+
+
+/*
+================
+InitMover
+
+"pos1", "pos2", and "speed" should be set before calling,
+so the movement delta can be calculated
+================
+*/
+void InitMover( gentity_t *ent )
+{
+ vec3_t move;
+ float distance;
+ float light;
+ vec3_t color;
+ qboolean lightSet, colorSet;
+ char *sound;
+
+ // if the "model2" key is set, use a seperate model
+ // for drawing, but clip against the brushes
+ if( ent->model2 )
+ ent->s.modelindex2 = G_ModelIndex( ent->model2 );
+
+ // if the "loopsound" key is set, use a constant looping sound when moving
+ if( G_SpawnString( "noise", "100", &sound ) )
+ ent->s.loopSound = G_SoundIndex( sound );
+
+ // if the "color" or "light" keys are set, setup constantLight
+ lightSet = G_SpawnFloat( "light", "100", &light );
+ colorSet = G_SpawnVector( "color", "1 1 1", color );
+
+ if( lightSet || colorSet )
+ {
+ int r, g, b, i;
+
+ r = color[ 0 ] * 255;
+ if( r > 255 )
+ r = 255;
+
+ g = color[ 1 ] * 255;
+ if( g > 255 )
+ g = 255;
+
+ b = color[ 2 ] * 255;
+ if( b > 255 )
+ b = 255;
+
+ i = light / 4;
+ if( i > 255 )
+ i = 255;
+
+ ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
+ }
+
+
+ ent->use = Use_BinaryMover;
+ ent->reached = Reached_BinaryMover;
+
+ ent->moverState = MOVER_POS1;
+ ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ ent->s.eType = ET_MOVER;
+ VectorCopy( ent->pos1, ent->r.currentOrigin );
+ trap_LinkEntity( ent );
+
+ ent->s.pos.trType = TR_STATIONARY;
+ VectorCopy( ent->pos1, ent->s.pos.trBase );
+
+ // calculate time to reach second position from speed
+ VectorSubtract( ent->pos2, ent->pos1, move );
+ distance = VectorLength( move );
+ if( !ent->speed )
+ ent->speed = 100;
+
+ VectorScale( move, ent->speed, ent->s.pos.trDelta );
+ ent->s.pos.trDuration = distance * 1000 / ent->speed;
+
+ if( ent->s.pos.trDuration <= 0 )
+ ent->s.pos.trDuration = 1;
+}
+
+
+/*
+================
+InitRotator
+
+"pos1", "pos2", and "speed" should be set before calling,
+so the movement delta can be calculated
+================
+*/
+void InitRotator( gentity_t *ent )
+{
+ vec3_t move;
+ float angle;
+ float light;
+ vec3_t color;
+ qboolean lightSet, colorSet;
+ char *sound;
+
+ // if the "model2" key is set, use a seperate model
+ // for drawing, but clip against the brushes
+ if( ent->model2 )
+ ent->s.modelindex2 = G_ModelIndex( ent->model2 );
+
+ // if the "loopsound" key is set, use a constant looping sound when moving
+ if( G_SpawnString( "noise", "100", &sound ) )
+ ent->s.loopSound = G_SoundIndex( sound );
+
+ // if the "color" or "light" keys are set, setup constantLight
+ lightSet = G_SpawnFloat( "light", "100", &light );
+ colorSet = G_SpawnVector( "color", "1 1 1", color );
+
+ if( lightSet || colorSet )
+ {
+ int r, g, b, i;
+
+ r = color[ 0 ] * 255;
+
+ if( r > 255 )
+ r = 255;
+
+ g = color[ 1 ] * 255;
+
+ if( g > 255 )
+ g = 255;
+
+ b = color[ 2 ] * 255;
+
+ if( b > 255 )
+ b = 255;
+
+ i = light / 4;
+
+ if( i > 255 )
+ i = 255;
+
+ ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
+ }
+
+
+ ent->use = Use_BinaryMover;
+ ent->reached = Reached_BinaryMover;
+
+ ent->moverState = ROTATOR_POS1;
+ ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+ ent->s.eType = ET_MOVER;
+ VectorCopy( ent->pos1, ent->r.currentAngles );
+ trap_LinkEntity( ent );
+
+ ent->s.apos.trType = TR_STATIONARY;
+ VectorCopy( ent->pos1, ent->s.apos.trBase );
+
+ // calculate time to reach second position from speed
+ VectorSubtract( ent->pos2, ent->pos1, move );
+ angle = VectorLength( move );
+
+ if( !ent->speed )
+ ent->speed = 120;
+
+ VectorScale( move, ent->speed, ent->s.apos.trDelta );
+ ent->s.apos.trDuration = angle * 1000 / ent->speed;
+
+ if( ent->s.apos.trDuration <= 0 )
+ ent->s.apos.trDuration = 1;
+}
+
+
+/*
+===============================================================================
+
+DOOR
+
+A use can be triggered either by a touch function, by being shot, or by being
+targeted by another entity.
+
+===============================================================================
+*/
+
+/*
+================
+Blocked_Door
+================
+*/
+void Blocked_Door( gentity_t *ent, gentity_t *other )
+{
+ // remove anything other than a client or buildable
+ if( !other->client && other->s.eType != ET_BUILDABLE )
+ {
+ G_FreeEntity( other );
+ return;
+ }
+
+ if( ent->damage )
+ G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
+
+ if( ent->spawnflags & 4 )
+ return; // crushers don't reverse
+
+ // reverse direction
+ Use_BinaryMover( ent, ent, other );
+}
+
+/*
+================
+Touch_DoorTriggerSpectator
+================
+*/
+static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ int i, axis;
+ vec3_t origin, dir, angles;
+
+ axis = ent->count;
+ VectorClear( dir );
+
+ if( fabs( other->s.origin[ axis ] - ent->r.absmax[ axis ] ) <
+ fabs( other->s.origin[ axis ] - ent->r.absmin[ axis ] ) )
+ {
+ origin[ axis ] = ent->r.absmin[ axis ] - 20;
+ dir[ axis ] = -1;
+ }
+ else
+ {
+ origin[ axis ] = ent->r.absmax[ axis ] + 20;
+ dir[ axis ] = 1;
+ }
+
+ for( i = 0; i < 3; i++ )
+ {
+ if( i == axis )
+ continue;
+
+ origin[ i ] = ( ent->r.absmin[ i ] + ent->r.absmax[ i ] ) * 0.5;
+ }
+
+ vectoangles( dir, angles );
+ TeleportPlayer( other, origin, angles );
+}
+
+
+/*
+================
+manualDoorTriggerSpectator
+
+This effectively creates a temporary door auto trigger so manually
+triggers doors can be skipped by spectators
+================
+*/
+static void manualDoorTriggerSpectator( gentity_t *door, gentity_t *player )
+{
+ gentity_t *other;
+ gentity_t triggerHull;
+ int best, i;
+ vec3_t mins, maxs;
+
+ //don't skip a door that is already open
+ if( door->moverState == MOVER_1TO2 ||
+ door->moverState == MOVER_POS2 ||
+ door->moverState == ROTATOR_1TO2 ||
+ door->moverState == ROTATOR_POS2 ||
+ door->moverState == MODEL_1TO2 ||
+ door->moverState == MODEL_POS2 )
+ return;
+
+ // find the bounds of everything on the team
+ VectorCopy( door->r.absmin, mins );
+ VectorCopy( door->r.absmax, maxs );
+
+ for( other = door->teamchain; other; other = other->teamchain )
+ {
+ AddPointToBounds( other->r.absmin, mins, maxs );
+ AddPointToBounds( other->r.absmax, mins, maxs );
+ }
+
+ // find the thinnest axis, which will be the one we expand
+ best = 0;
+ for( i = 1; i < 3; i++ )
+ {
+ if( maxs[ i ] - mins[ i ] < maxs[ best ] - mins[ best ] )
+ best = i;
+ }
+
+ maxs[ best ] += 60;
+ mins[ best ] -= 60;
+
+ VectorCopy( mins, triggerHull.r.absmin );
+ VectorCopy( maxs, triggerHull.r.absmax );
+ triggerHull.count = best;
+
+ Touch_DoorTriggerSpectator( &triggerHull, player, NULL );
+}
+
+/*
+================
+manualTriggerSpectator
+
+Trip to skip the closest door targetted by trigger
+================
+*/
+void manualTriggerSpectator( gentity_t *trigger, gentity_t *player )
+{
+ gentity_t *t = NULL;
+ gentity_t *targets[ MAX_GENTITIES ];
+ int i = 0, j;
+ float minDistance = (float)INFINITE;
+
+ //restrict this hack to trigger_multiple only for now
+ if( strcmp( trigger->classname, "trigger_multiple" ) )
+ return;
+
+ if( !trigger->target )
+ return;
+
+ //create a list of door entities this trigger targets
+ while( ( t = G_Find( t, FOFS( targetname ), trigger->target ) ) != NULL )
+ {
+ if( !strcmp( t->classname, "func_door" ) )
+ targets[ i++ ] = t;
+ else if( t == trigger )
+ G_Printf( "WARNING: Entity used itself.\n" );
+
+ if( !trigger->inuse )
+ {
+ G_Printf( "triggerity was removed while using targets\n" );
+ return;
+ }
+ }
+
+ //if more than 0 targets
+ if( i > 0 )
+ {
+ gentity_t *closest = NULL;
+
+ //pick the closest door
+ for( j = 0; j < i; j++ )
+ {
+ float d = Distance( player->r.currentOrigin, targets[ j ]->r.currentOrigin );
+
+ if( d < minDistance )
+ {
+ minDistance = d;
+ closest = targets[ j ];
+ }
+ }
+
+ //try and skip the door
+ manualDoorTriggerSpectator( closest, player );
+ }
+}
+
+
+/*
+================
+Touch_DoorTrigger
+================
+*/
+void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ //buildables don't trigger movers
+ if( other->s.eType == ET_BUILDABLE )
+ return;
+
+ if( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR )
+ {
+ // if the door is not open and not opening
+ if( ent->parent->moverState != MOVER_1TO2 &&
+ ent->parent->moverState != MOVER_POS2 &&
+ ent->parent->moverState != ROTATOR_1TO2 &&
+ ent->parent->moverState != ROTATOR_POS2 )
+ Touch_DoorTriggerSpectator( ent, other, trace );
+ }
+ else if( ent->parent->moverState != MOVER_1TO2 &&
+ ent->parent->moverState != ROTATOR_1TO2 &&
+ ent->parent->moverState != ROTATOR_2TO1 )
+ {
+ Use_BinaryMover( ent->parent, ent, other );
+ }
+}
+
+
+/*
+======================
+Think_SpawnNewDoorTrigger
+
+All of the parts of a door have been spawned, so create
+a trigger that encloses all of them
+======================
+*/
+void Think_SpawnNewDoorTrigger( gentity_t *ent )
+{
+ gentity_t *other;
+ vec3_t mins, maxs;
+ int i, best;
+
+ //TA: disable shootable doors
+ // set all of the slaves as shootable
+ //for( other = ent; other; other = other->teamchain )
+ // other->takedamage = qtrue;
+
+ // find the bounds of everything on the team
+ VectorCopy( ent->r.absmin, mins );
+ VectorCopy( ent->r.absmax, maxs );
+
+ for( other = ent->teamchain; other; other=other->teamchain )
+ {
+ AddPointToBounds( other->r.absmin, mins, maxs );
+ AddPointToBounds( other->r.absmax, mins, maxs );
+ }
+
+ // find the thinnest axis, which will be the one we expand
+ best = 0;
+ for( i = 1; i < 3; i++ )
+ {
+ if( maxs[ i ] - mins[ i ] < maxs[ best ] - mins[ best ] )
+ best = i;
+ }
+
+ maxs[ best ] += 60;
+ mins[ best ] -= 60;
+
+ // create a trigger with this size
+ other = G_Spawn( );
+ other->classname = "door_trigger";
+ VectorCopy( mins, other->r.mins );
+ VectorCopy( maxs, other->r.maxs );
+ other->parent = ent;
+ other->r.contents = CONTENTS_TRIGGER;
+ other->touch = Touch_DoorTrigger;
+ // remember the thinnest axis
+ other->count = best;
+ trap_LinkEntity( other );
+
+ if( ent->moverState < MODEL_POS1 )
+ MatchTeam( ent, ent->moverState, level.time );
+}
+
+void Think_MatchTeam( gentity_t *ent )
+{
+ MatchTeam( ent, ent->moverState, level.time );
+}
+
+
+/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER
+TOGGLE wait in both the start and end states for a trigger event.
+START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
+NOMONSTER monsters will not trigger this door
+
+"model2" .md3 model to also draw
+"angle" determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"speed" movement speed (100 default)
+"wait" wait before returning (3 default, -1 = never return)
+"lip" lip remaining at end of move (8 default)
+"dmg" damage to inflict when blocked (2 default)
+"color" constantLight color
+"light" constantLight radius
+"health" if set, the door must be shot open
+*/
+void SP_func_door( gentity_t *ent )
+{
+ vec3_t abs_movedir;
+ float distance;
+ vec3_t size;
+ float lip;
+ char *s;
+
+ G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s );
+ ent->sound2to1 = G_SoundIndex( s );
+ G_SpawnString( "sound1to2", "sound/movers/doors/dr1_strt.wav", &s );
+ ent->sound1to2 = G_SoundIndex( s );
+
+ G_SpawnString( "soundPos2", "sound/movers/doors/dr1_end.wav", &s );
+ ent->soundPos2 = G_SoundIndex( s );
+ G_SpawnString( "soundPos1", "sound/movers/doors/dr1_end.wav", &s );
+ ent->soundPos1 = G_SoundIndex( s );
+
+ ent->blocked = Blocked_Door;
+
+ // default speed of 400
+ if( !ent->speed )
+ ent->speed = 400;
+
+ // default wait of 2 seconds
+ if( !ent->wait )
+ ent->wait = 2;
+
+ ent->wait *= 1000;
+
+ // default lip of 8 units
+ G_SpawnFloat( "lip", "8", &lip );
+
+ // default damage of 2 points
+ G_SpawnInt( "dmg", "2", &ent->damage );
+
+ // first position at start
+ VectorCopy( ent->s.origin, ent->pos1 );
+
+ // calculate second position
+ trap_SetBrushModel( ent, ent->model );
+ G_SetMovedir( ent->s.angles, ent->movedir );
+ abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] );
+ abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] );
+ abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] );
+ VectorSubtract( ent->r.maxs, ent->r.mins, size );
+ distance = DotProduct( abs_movedir, size ) - lip;
+ VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
+
+ // if "start_open", reverse position 1 and 2
+ if( ent->spawnflags & 1 )
+ {
+ vec3_t temp;
+
+ VectorCopy( ent->pos2, temp );
+ VectorCopy( ent->s.origin, ent->pos2 );
+ VectorCopy( temp, ent->pos1 );
+ }
+
+ InitMover( ent );
+
+ ent->nextthink = level.time + FRAMETIME;
+
+ if( !( ent->flags & FL_TEAMSLAVE ) )
+ {
+ int health;
+
+ G_SpawnInt( "health", "0", &health );
+ if( health )
+ ent->takedamage = qtrue;
+
+ if( ent->targetname || health )
+ {
+ // non touch/shoot doors
+ ent->think = Think_MatchTeam;
+ }
+ else
+ ent->think = Think_SpawnNewDoorTrigger;
+ }
+}
+
+/*QUAKED func_door_rotating (0 .5 .8) START_OPEN CRUSHER REVERSE TOGGLE X_AXIS Y_AXIS
+ * This is the rotating door... just as the name suggests it's a door that rotates
+ * START_OPEN the door to moves to its destination when spawned, and operate in reverse.
+ * REVERSE if you want the door to open in the other direction, use this switch.
+ * TOGGLE wait in both the start and end states for a trigger event.
+ * X_AXIS open on the X-axis instead of the Z-axis
+ * Y_AXIS open on the Y-axis instead of the Z-axis
+ *
+ * You need to have an origin brush as part of this entity. The center of that brush will be
+ * the point around which it is rotated. It will rotate around the Z axis by default. You can
+ * check either the X_AXIS or Y_AXIS box to change that.
+ *
+ * "model2" .md3 model to also draw
+ * "distance" how many degrees the door will open
+ * "speed" how fast the door will open (degrees/second)
+ * "color" constantLight color
+ * "light" constantLight radius
+ * */
+
+void SP_func_door_rotating( gentity_t *ent )
+{
+ char *s;
+
+ G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s );
+ ent->sound2to1 = G_SoundIndex( s );
+ G_SpawnString( "sound1to2", "sound/movers/doors/dr1_strt.wav", &s );
+ ent->sound1to2 = G_SoundIndex( s );
+
+ G_SpawnString( "soundPos2", "sound/movers/doors/dr1_end.wav", &s );
+ ent->soundPos2 = G_SoundIndex( s );
+ G_SpawnString( "soundPos1", "sound/movers/doors/dr1_end.wav", &s );
+ ent->soundPos1 = G_SoundIndex( s );
+
+ ent->blocked = Blocked_Door;
+
+ //default speed of 120
+ if( !ent->speed )
+ ent->speed = 120;
+
+ // if speed is negative, positize it and add reverse flag
+ if( ent->speed < 0 )
+ {
+ ent->speed *= -1;
+ ent->spawnflags |= 8;
+ }
+
+ // default of 2 seconds
+ if( !ent->wait )
+ ent->wait = 2;
+
+ ent->wait *= 1000;
+
+ // set the axis of rotation
+ VectorClear( ent->movedir );
+ VectorClear( ent->s.angles );
+
+ if( ent->spawnflags & 32 )
+ ent->movedir[ 2 ] = 1.0;
+ else if( ent->spawnflags & 64 )
+ ent->movedir[ 0 ] = 1.0;
+ else
+ ent->movedir[ 1 ] = 1.0;
+
+ // reverse direction if necessary
+ if( ent->spawnflags & 8 )
+ VectorNegate ( ent->movedir, ent->movedir );
+
+ // default distance of 90 degrees. This is something the mapper should not
+ // leave out, so we'll tell him if he does.
+ if( !ent->rotatorAngle )
+ {
+ G_Printf( "%s at %s with no rotatorAngle set.\n",
+ ent->classname, vtos( ent->s.origin ) );
+
+ ent->rotatorAngle = 90.0;
+ }
+
+ VectorCopy( ent->s.angles, ent->pos1 );
+ trap_SetBrushModel( ent, ent->model );
+ VectorMA( ent->pos1, ent->rotatorAngle, ent->movedir, ent->pos2 );
+
+ // if "start_open", reverse position 1 and 2
+ if( ent->spawnflags & 1 )
+ {
+ vec3_t temp;
+
+ VectorCopy( ent->pos2, temp );
+ VectorCopy( ent->s.angles, ent->pos2 );
+ VectorCopy( temp, ent->pos1 );
+ VectorNegate( ent->movedir, ent->movedir );
+ }
+
+ // set origin
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
+
+ InitRotator( ent );
+
+ ent->nextthink = level.time + FRAMETIME;
+
+ if( !( ent->flags & FL_TEAMSLAVE ) )
+ {
+ int health;
+
+ G_SpawnInt( "health", "0", &health );
+
+ if( health )
+ ent->takedamage = qtrue;
+
+ if( ent->targetname || health )
+ {
+ // non touch/shoot doors
+ ent->think = Think_MatchTeam;
+ }
+ else
+ ent->think = Think_SpawnNewDoorTrigger;
+ }
+}
+
+/*QUAKED func_door_model (0 .5 .8) ? START_OPEN
+TOGGLE wait in both the start and end states for a trigger event.
+START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
+NOMONSTER monsters will not trigger this door
+
+"model2" .md3 model to also draw
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"speed" movement speed (100 default)
+"wait" wait before returning (3 default, -1 = never return)
+"color" constantLight color
+"light" constantLight radius
+"health" if set, the door must be shot open
+*/
+void SP_func_door_model( gentity_t *ent )
+{
+ char *s;
+ float light;
+ vec3_t color;
+ qboolean lightSet, colorSet;
+ char *sound;
+ gentity_t *clipBrush;
+
+ G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s );
+ ent->sound2to1 = G_SoundIndex( s );
+ G_SpawnString( "sound1to2", "sound/movers/doors/dr1_strt.wav", &s );
+ ent->sound1to2 = G_SoundIndex( s );
+
+ G_SpawnString( "soundPos2", "sound/movers/doors/dr1_end.wav", &s );
+ ent->soundPos2 = G_SoundIndex( s );
+ G_SpawnString( "soundPos1", "sound/movers/doors/dr1_end.wav", &s );
+ ent->soundPos1 = G_SoundIndex( s );
+
+ //default speed of 100ms
+ if( !ent->speed )
+ ent->speed = 200;
+
+ //default wait of 2 seconds
+ if( ent->wait <= 0 )
+ ent->wait = 2;
+
+ ent->wait *= 1000;
+
+ //brush model
+ clipBrush = ent->clipBrush = G_Spawn( );
+ clipBrush->model = ent->model;
+ trap_SetBrushModel( clipBrush, clipBrush->model );
+ clipBrush->s.eType = ET_INVISIBLE;
+ trap_LinkEntity( clipBrush );
+
+ //copy the bounds back from the clipBrush so the
+ //triggers can be made
+ VectorCopy( clipBrush->r.absmin, ent->r.absmin );
+ VectorCopy( clipBrush->r.absmax, ent->r.absmax );
+ VectorCopy( clipBrush->r.mins, ent->r.mins );
+ VectorCopy( clipBrush->r.maxs, ent->r.maxs );
+
+ G_SpawnVector( "modelOrigin", "0 0 0", ent->s.origin );
+
+ G_SpawnVector( "scale", "1 1 1", ent->s.origin2 );
+
+ // if the "model2" key is set, use a seperate model
+ // for drawing, but clip against the brushes
+ if( !ent->model2 )
+ G_Printf( S_COLOR_YELLOW "WARNING: func_door_model %d spawned with no model2 key\n", ent->s.number );
+ else
+ ent->s.modelindex = G_ModelIndex( ent->model2 );
+
+ // if the "loopsound" key is set, use a constant looping sound when moving
+ if( G_SpawnString( "noise", "100", &sound ) )
+ ent->s.loopSound = G_SoundIndex( sound );
+
+ // if the "color" or "light" keys are set, setup constantLight
+ lightSet = G_SpawnFloat( "light", "100", &light );
+ colorSet = G_SpawnVector( "color", "1 1 1", color );
+
+ if( lightSet || colorSet )
+ {
+ int r, g, b, i;
+
+ r = color[ 0 ] * 255;
+ if( r > 255 )
+ r = 255;
+
+ g = color[ 1 ] * 255;
+ if( g > 255 )
+ g = 255;
+
+ b = color[ 2 ] * 255;
+ if( b > 255 )
+ b = 255;
+
+ i = light / 4;
+ if( i > 255 )
+ i = 255;
+
+ ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
+ }
+
+ ent->use = Use_BinaryMover;
+
+ ent->moverState = MODEL_POS1;
+ ent->s.eType = ET_MODELDOOR;
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ ent->s.pos.trType = TR_STATIONARY;
+ ent->s.pos.trTime = 0;
+ ent->s.pos.trDuration = 0;
+ VectorClear( ent->s.pos.trDelta );
+ VectorCopy( ent->s.angles, ent->s.apos.trBase );
+ ent->s.apos.trType = TR_STATIONARY;
+ ent->s.apos.trTime = 0;
+ ent->s.apos.trDuration = 0;
+ VectorClear( ent->s.apos.trDelta );
+
+ ent->s.misc = (int)ent->animation[ 0 ]; //first frame
+ ent->s.weapon = abs( (int)ent->animation[ 1 ] ); //number of frames
+
+ //must be at least one frame -- mapper has forgotten animation key
+ if( ent->s.weapon == 0 )
+ ent->s.weapon = 1;
+
+ ent->s.torsoAnim = ent->s.weapon * ( 1000.0f / ent->speed ); //framerate
+
+ trap_LinkEntity( ent );
+
+ if( !( ent->flags & FL_TEAMSLAVE ) )
+ {
+ int health;
+
+ G_SpawnInt( "health", "0", &health );
+ if( health )
+ ent->takedamage = qtrue;
+
+ if( !( ent->targetname || health ) )
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = Think_SpawnNewDoorTrigger;
+ }
+ }
+}
+
+/*
+===============================================================================
+
+PLAT
+
+===============================================================================
+*/
+
+/*
+==============
+Touch_Plat
+
+Don't allow decent if a living player is on it
+===============
+*/
+void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ // DONT_WAIT
+ if( ent->spawnflags & 1 )
+ return;
+
+ if( !other->client || other->client->ps.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ // delay return-to-pos1 by one second
+ if( ent->moverState == MOVER_POS2 )
+ ent->nextthink = level.time + 1000;
+}
+
+/*
+==============
+Touch_PlatCenterTrigger
+
+If the plat is at the bottom position, start it going up
+===============
+*/
+void Touch_PlatCenterTrigger( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ if( !other->client )
+ return;
+
+ if( ent->parent->moverState == MOVER_POS1 )
+ Use_BinaryMover( ent->parent, ent, other );
+}
+
+
+/*
+================
+SpawnPlatTrigger
+
+Spawn a trigger in the middle of the plat's low position
+Elevator cars require that the trigger extend through the entire low position,
+not just sit on top of it.
+================
+*/
+void SpawnPlatTrigger( gentity_t *ent )
+{
+ gentity_t *trigger;
+ vec3_t tmin, tmax;
+
+ // the middle trigger will be a thin trigger just
+ // above the starting position
+ trigger = G_Spawn( );
+ trigger->classname = "plat_trigger";
+ trigger->touch = Touch_PlatCenterTrigger;
+ trigger->r.contents = CONTENTS_TRIGGER;
+ trigger->parent = ent;
+
+ tmin[ 0 ] = ent->pos1[ 0 ] + ent->r.mins[ 0 ] + 33;
+ tmin[ 1 ] = ent->pos1[ 1 ] + ent->r.mins[ 1 ] + 33;
+ tmin[ 2 ] = ent->pos1[ 2 ] + ent->r.mins[ 2 ];
+
+ tmax[ 0 ] = ent->pos1[ 0 ] + ent->r.maxs[ 0 ] - 33;
+ tmax[ 1 ] = ent->pos1[ 1 ] + ent->r.maxs[ 1 ] - 33;
+ tmax[ 2 ] = ent->pos1[ 2 ] + ent->r.maxs[ 2 ] + 8;
+
+ if( tmax[ 0 ] <= tmin[ 0 ] )
+ {
+ tmin[ 0 ] = ent->pos1[ 0 ] + ( ent->r.mins[ 0 ] + ent->r.maxs[ 0 ] ) * 0.5;
+ tmax[ 0 ] = tmin[ 0 ] + 1;
+ }
+
+ if( tmax[ 1 ] <= tmin[ 1 ] )
+ {
+ tmin[ 1 ] = ent->pos1[ 1 ] + ( ent->r.mins[ 1 ] + ent->r.maxs[ 1 ] ) * 0.5;
+ tmax[ 1 ] = tmin[ 1 ] + 1;
+ }
+
+ VectorCopy( tmin, trigger->r.mins );
+ VectorCopy( tmax, trigger->r.maxs );
+
+ trap_LinkEntity( trigger );
+}
+
+
+/*QUAKED func_plat (0 .5 .8) ?
+Plats are always drawn in the extended position so they will light correctly.
+
+"lip" default 8, protrusion above rest position
+"height" total height of movement, defaults to model height
+"speed" overrides default 200.
+"dmg" overrides default 2
+"model2" .md3 model to also draw
+"color" constantLight color
+"light" constantLight radius
+*/
+void SP_func_plat( gentity_t *ent )
+{
+ float lip, height;
+ char *s;
+
+ G_SpawnString( "sound2to1", "sound/movers/plats/pt1_strt.wav", &s );
+ ent->sound2to1 = G_SoundIndex( s );
+ G_SpawnString( "sound1to2", "sound/movers/plats/pt1_strt.wav", &s );
+ ent->sound1to2 = G_SoundIndex( s );
+
+ G_SpawnString( "soundPos2", "sound/movers/plats/pt1_end.wav", &s );
+ ent->soundPos2 = G_SoundIndex( s );
+ G_SpawnString( "soundPos1", "sound/movers/plats/pt1_end.wav", &s );
+ ent->soundPos1 = G_SoundIndex( s );
+
+ VectorClear( ent->s.angles );
+
+ G_SpawnFloat( "speed", "200", &ent->speed );
+ G_SpawnInt( "dmg", "2", &ent->damage );
+ G_SpawnFloat( "wait", "1", &ent->wait );
+ G_SpawnFloat( "lip", "8", &lip );
+
+ ent->wait = 1000;
+
+ // create second position
+ trap_SetBrushModel( ent, ent->model );
+
+ if( !G_SpawnFloat( "height", "0", &height ) )
+ height = ( ent->r.maxs[ 2 ] - ent->r.mins[ 2 ] ) - lip;
+
+ // pos1 is the rest (bottom) position, pos2 is the top
+ VectorCopy( ent->s.origin, ent->pos2 );
+ VectorCopy( ent->pos2, ent->pos1 );
+ ent->pos1[ 2 ] -= height;
+
+ InitMover( ent );
+
+ // touch function keeps the plat from returning while
+ // a live player is standing on it
+ ent->touch = Touch_Plat;
+
+ ent->blocked = Blocked_Door;
+
+ ent->parent = ent; // so it can be treated as a door
+
+ // spawn the trigger if one hasn't been custom made
+ if( !ent->targetname )
+ SpawnPlatTrigger( ent );
+}
+
+
+/*
+===============================================================================
+
+BUTTON
+
+===============================================================================
+*/
+
+/*
+==============
+Touch_Button
+
+===============
+*/
+void Touch_Button( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ if( !other->client )
+ return;
+
+ if( ent->moverState == MOVER_POS1 )
+ Use_BinaryMover( ent, other, other );
+}
+
+
+/*QUAKED func_button (0 .5 .8) ?
+When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
+
+"model2" .md3 model to also draw
+"angle" determines the opening direction
+"target" all entities with a matching targetname will be used
+"speed" override the default 40 speed
+"wait" override the default 1 second wait (-1 = never return)
+"lip" override the default 4 pixel lip remaining at end of move
+"health" if set, the button must be killed instead of touched
+"color" constantLight color
+"light" constantLight radius
+*/
+void SP_func_button( gentity_t *ent )
+{
+ vec3_t abs_movedir;
+ float distance;
+ vec3_t size;
+ float lip;
+ char *s;
+
+ G_SpawnString( "sound1to2", "sound/movers/switches/button1.wav", &s );
+ ent->sound1to2 = G_SoundIndex( s );
+
+ if( !ent->speed )
+ ent->speed = 40;
+
+ if( !ent->wait )
+ ent->wait = 1;
+
+ ent->wait *= 1000;
+
+ // first position
+ VectorCopy( ent->s.origin, ent->pos1 );
+
+ // calculate second position
+ trap_SetBrushModel( ent, ent->model );
+
+ G_SpawnFloat( "lip", "4", &lip );
+
+ G_SetMovedir( ent->s.angles, ent->movedir );
+ abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] );
+ abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] );
+ abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] );
+ VectorSubtract( ent->r.maxs, ent->r.mins, size );
+ distance = abs_movedir[ 0 ] * size[ 0 ] + abs_movedir[ 1 ] * size[ 1 ] + abs_movedir[ 2 ] * size[ 2 ] - lip;
+ VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
+
+ if( ent->health )
+ {
+ // shootable button
+ ent->takedamage = qtrue;
+ }
+ else
+ {
+ // touchable button
+ ent->touch = Touch_Button;
+ }
+
+ InitMover( ent );
+}
+
+
+
+/*
+===============================================================================
+
+TRAIN
+
+===============================================================================
+*/
+
+
+#define TRAIN_START_OFF 1
+#define TRAIN_BLOCK_STOPS 2
+
+/*
+===============
+Think_BeginMoving
+
+The wait time at a corner has completed, so start moving again
+===============
+*/
+void Think_BeginMoving( gentity_t *ent )
+{
+ ent->s.pos.trTime = level.time;
+ ent->s.pos.trType = TR_LINEAR_STOP;
+}
+
+/*
+===============
+Reached_Train
+===============
+*/
+void Reached_Train( gentity_t *ent )
+{
+ gentity_t *next;
+ float speed;
+ vec3_t move;
+ float length;
+
+ // copy the apropriate values
+ next = ent->nextTrain;
+ if( !next || !next->nextTrain )
+ return; // just stop
+
+ // fire all other targets
+ G_UseTargets( next, NULL );
+
+ // set the new trajectory
+ ent->nextTrain = next->nextTrain;
+ VectorCopy( next->s.origin, ent->pos1 );
+ VectorCopy( next->nextTrain->s.origin, ent->pos2 );
+
+ // if the path_corner has a speed, use that
+ if( next->speed )
+ {
+ speed = next->speed;
+ }
+ else
+ {
+ // otherwise use the train's speed
+ speed = ent->speed;
+ }
+
+ if( speed < 1 )
+ speed = 1;
+
+ ent->lastSpeed = speed;
+
+ // calculate duration
+ VectorSubtract( ent->pos2, ent->pos1, move );
+ length = VectorLength( move );
+
+ ent->s.pos.trDuration = length * 1000 / speed;
+
+ // looping sound
+ ent->s.loopSound = next->soundLoop;
+
+ // start it going
+ SetMoverState( ent, MOVER_1TO2, level.time );
+
+ if( ent->spawnflags & TRAIN_START_OFF )
+ {
+ ent->s.pos.trType = TR_STATIONARY;
+ return;
+ }
+
+ // if there is a "wait" value on the target, don't start moving yet
+ if( next->wait )
+ {
+ ent->nextthink = level.time + next->wait * 1000;
+ ent->think = Think_BeginMoving;
+ ent->s.pos.trType = TR_STATIONARY;
+ }
+}
+
+/*
+================
+Start_Train
+================
+*/
+void Start_Train( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ vec3_t move;
+
+ //recalculate duration as the mover is highly
+ //unlikely to be right on a path_corner
+ VectorSubtract( ent->pos2, ent->pos1, move );
+ ent->s.pos.trDuration = VectorLength( move ) * 1000 / ent->lastSpeed;
+ SetMoverState( ent, MOVER_1TO2, level.time );
+
+ ent->spawnflags &= ~TRAIN_START_OFF;
+}
+
+/*
+================
+Stop_Train
+================
+*/
+void Stop_Train( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ vec3_t origin;
+
+ //get current origin
+ BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
+ VectorCopy( origin, ent->pos1 );
+ SetMoverState( ent, MOVER_POS1, level.time );
+
+ ent->spawnflags |= TRAIN_START_OFF;
+}
+
+/*
+================
+Use_Train
+================
+*/
+void Use_Train( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ if( ent->spawnflags & TRAIN_START_OFF )
+ {
+ //train is currently not moving so start it
+ Start_Train( ent, other, activator );
+ }
+ else
+ {
+ //train is moving so stop it
+ Stop_Train( ent, other, activator );
+ }
+}
+
+/*
+===============
+Think_SetupTrainTargets
+
+Link all the corners together
+===============
+*/
+void Think_SetupTrainTargets( gentity_t *ent )
+{
+ gentity_t *path, *next, *start;
+
+ ent->nextTrain = G_Find( NULL, FOFS( targetname ), ent->target );
+
+ if( !ent->nextTrain )
+ {
+ G_Printf( "func_train at %s with an unfound target\n",
+ vtos( ent->r.absmin ) );
+ return;
+ }
+
+ start = NULL;
+ for( path = ent->nextTrain; path != start; path = next )
+ {
+ if( !start )
+ start = path;
+
+ if( !path->target )
+ {
+ G_Printf( "Train corner at %s without a target\n",
+ vtos( path->s.origin ) );
+ return;
+ }
+
+ // find a path_corner among the targets
+ // there may also be other targets that get fired when the corner
+ // is reached
+ next = NULL;
+ do
+ {
+ next = G_Find( next, FOFS( targetname ), path->target );
+
+ if( !next )
+ {
+ G_Printf( "Train corner at %s without a target path_corner\n",
+ vtos( path->s.origin ) );
+ return;
+ }
+ } while( strcmp( next->classname, "path_corner" ) );
+
+ path->nextTrain = next;
+ }
+
+ // start the train moving from the first corner
+ Reached_Train( ent );
+}
+
+
+
+/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8)
+Train path corners.
+Target: next path corner and other targets to fire
+"speed" speed to move to the next corner
+"wait" seconds to wait before behining move to next corner
+*/
+void SP_path_corner( gentity_t *self )
+{
+ if( !self->targetname )
+ {
+ G_Printf( "path_corner with no targetname at %s\n", vtos( self->s.origin ) );
+ G_FreeEntity( self );
+ return;
+ }
+ // path corners don't need to be linked in
+}
+
+/*
+================
+Blocked_Train
+================
+*/
+void Blocked_Train( gentity_t *self, gentity_t *other )
+{
+ if( self->spawnflags & TRAIN_BLOCK_STOPS )
+ Stop_Train( self, other, other );
+ else
+ {
+ if( !other->client )
+ {
+ //whatever is blocking the train isn't a client
+
+ //KILL!!1!!!
+ G_Damage( other, self, self, NULL, NULL, 10000, 0, MOD_CRUSH );
+
+ //buildables need to be handled differently since even when
+ //dealth fatal amounts of damage they won't instantly become non-solid
+ if( other->s.eType == ET_BUILDABLE && other->spawned )
+ {
+ vec3_t dir;
+ gentity_t *tent;
+
+ if( other->biteam == BIT_ALIENS )
+ {
+ VectorCopy( other->s.origin2, dir );
+ tent = G_TempEntity( other->s.origin, EV_ALIEN_BUILDABLE_EXPLOSION );
+ tent->s.eventParm = DirToByte( dir );
+ }
+ else if( other->biteam == BIT_HUMANS )
+ {
+ VectorSet( dir, 0.0f, 0.0f, 1.0f );
+ tent = G_TempEntity( other->s.origin, EV_HUMAN_BUILDABLE_EXPLOSION );
+ tent->s.eventParm = DirToByte( dir );
+ }
+ }
+
+ //if it's still around free it
+ if( other )
+ G_FreeEntity( other );
+
+ return;
+ }
+
+ G_Damage( other, self, self, NULL, NULL, 10000, 0, MOD_CRUSH );
+ }
+}
+
+
+/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
+A train is a mover that moves between path_corner target points.
+Trains MUST HAVE AN ORIGIN BRUSH.
+The train spawns at the first target it is pointing at.
+"model2" .md3 model to also draw
+"speed" default 100
+"dmg" default 2
+"noise" looping sound to play when the train is in motion
+"target" next path corner
+"color" constantLight color
+"light" constantLight radius
+*/
+void SP_func_train( gentity_t *self )
+{
+ VectorClear( self->s.angles );
+
+ if( self->spawnflags & TRAIN_BLOCK_STOPS )
+ self->damage = 0;
+ else if( !self->damage )
+ self->damage = 2;
+
+ if( !self->speed )
+ self->speed = 100;
+
+ if( !self->target )
+ {
+ G_Printf( "func_train without a target at %s\n", vtos( self->r.absmin ) );
+ G_FreeEntity( self );
+ return;
+ }
+
+ trap_SetBrushModel( self, self->model );
+ InitMover( self );
+
+ self->reached = Reached_Train;
+ self->use = Use_Train;
+ self->blocked = Blocked_Train;
+
+ // start trains on the second frame, to make sure their targets have had
+ // a chance to spawn
+ self->nextthink = level.time + FRAMETIME;
+ self->think = Think_SetupTrainTargets;
+}
+
+/*
+===============================================================================
+
+STATIC
+
+===============================================================================
+*/
+
+
+/*QUAKED func_static (0 .5 .8) ?
+A bmodel that just sits there, doing nothing. Can be used for conditional walls and models.
+"model2" .md3 model to also draw
+"color" constantLight color
+"light" constantLight radius
+*/
+void SP_func_static( gentity_t *ent )
+{
+ trap_SetBrushModel( ent, ent->model );
+ InitMover( ent );
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ VectorCopy( ent->s.origin, ent->r.currentOrigin );
+}
+
+
+/*
+===============================================================================
+
+ROTATING
+
+===============================================================================
+*/
+
+
+/*QUAKED func_rotating (0 .5 .8) ? START_ON - X_AXIS Y_AXIS
+You need to have an origin brush as part of this entity. The center of that brush will be
+the point around which it is rotated. It will rotate around the Z axis by default. You can
+check either the X_AXIS or Y_AXIS box to change that.
+
+"model2" .md3 model to also draw
+"speed" determines how fast it moves; default value is 100.
+"dmg" damage to inflict when blocked (2 default)
+"color" constantLight color
+"light" constantLight radius
+*/
+void SP_func_rotating( gentity_t *ent )
+{
+ if( !ent->speed )
+ ent->speed = 100;
+
+ // set the axis of rotation
+ ent->s.apos.trType = TR_LINEAR;
+
+ if( ent->spawnflags & 4 )
+ ent->s.apos.trDelta[ 2 ] = ent->speed;
+ else if( ent->spawnflags & 8 )
+ ent->s.apos.trDelta[ 0 ] = ent->speed;
+ else
+ ent->s.apos.trDelta[ 1 ] = ent->speed;
+
+ if( !ent->damage )
+ ent->damage = 2;
+
+ trap_SetBrushModel( ent, ent->model );
+ InitMover( ent );
+
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
+ VectorCopy( ent->s.apos.trBase, ent->r.currentAngles );
+
+ trap_LinkEntity( ent );
+}
+
+
+/*
+===============================================================================
+
+BOBBING
+
+===============================================================================
+*/
+
+
+/*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
+Normally bobs on the Z axis
+"model2" .md3 model to also draw
+"height" amplitude of bob (32 default)
+"speed" seconds to complete a bob cycle (4 default)
+"phase" the 0.0 to 1.0 offset in the cycle to start at
+"dmg" damage to inflict when blocked (2 default)
+"color" constantLight color
+"light" constantLight radius
+*/
+void SP_func_bobbing( gentity_t *ent )
+{
+ float height;
+ float phase;
+
+ G_SpawnFloat( "speed", "4", &ent->speed );
+ G_SpawnFloat( "height", "32", &height );
+ G_SpawnInt( "dmg", "2", &ent->damage );
+ G_SpawnFloat( "phase", "0", &phase );
+
+ trap_SetBrushModel( ent, ent->model );
+ InitMover( ent );
+
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ VectorCopy( ent->s.origin, ent->r.currentOrigin );
+
+ ent->s.pos.trDuration = ent->speed * 1000;
+ ent->s.pos.trTime = ent->s.pos.trDuration * phase;
+ ent->s.pos.trType = TR_SINE;
+
+ // set the axis of bobbing
+ if( ent->spawnflags & 1 )
+ ent->s.pos.trDelta[ 0 ] = height;
+ else if( ent->spawnflags & 2 )
+ ent->s.pos.trDelta[ 1 ] = height;
+ else
+ ent->s.pos.trDelta[ 2 ] = height;
+}
+
+/*
+===============================================================================
+
+PENDULUM
+
+===============================================================================
+*/
+
+
+/*QUAKED func_pendulum (0 .5 .8) ?
+You need to have an origin brush as part of this entity.
+Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions.
+Pendulum frequency is a physical constant based on the length of the beam and gravity.
+"model2" .md3 model to also draw
+"speed" the number of degrees each way the pendulum swings, (30 default)
+"phase" the 0.0 to 1.0 offset in the cycle to start at
+"dmg" damage to inflict when blocked (2 default)
+"color" constantLight color
+"light" constantLight radius
+*/
+void SP_func_pendulum( gentity_t *ent )
+{
+ float freq;
+ float length;
+ float phase;
+ float speed;
+
+ G_SpawnFloat( "speed", "30", &speed );
+ G_SpawnInt( "dmg", "2", &ent->damage );
+ G_SpawnFloat( "phase", "0", &phase );
+
+ trap_SetBrushModel( ent, ent->model );
+
+ // find pendulum length
+ length = fabs( ent->r.mins[ 2 ] );
+
+ if( length < 8 )
+ length = 8;
+
+ freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) );
+
+ ent->s.pos.trDuration = ( 1000 / freq );
+
+ InitMover( ent );
+
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ VectorCopy( ent->s.origin, ent->r.currentOrigin );
+
+ VectorCopy( ent->s.angles, ent->s.apos.trBase );
+
+ ent->s.apos.trDuration = 1000 / freq;
+ ent->s.apos.trTime = ent->s.apos.trDuration * phase;
+ ent->s.apos.trType = TR_SINE;
+ ent->s.apos.trDelta[ 2 ] = speed;
+}
diff --git a/src/game/g_physics.c b/src/game/g_physics.c
new file mode 100644
index 0000000..58c6487
--- /dev/null
+++ b/src/game/g_physics.c
@@ -0,0 +1,167 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+/*
+================
+G_Bounce
+
+================
+*/
+static void G_Bounce( gentity_t *ent, trace_t *trace )
+{
+ vec3_t velocity;
+ float dot;
+ int hitTime;
+ float minNormal;
+ qboolean invert = qfalse;
+
+ // reflect the velocity on the trace plane
+ hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
+ BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
+ dot = DotProduct( velocity, trace->plane.normal );
+ VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
+
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ minNormal = BG_FindMinNormalForBuildable( ent->s.modelindex );
+ invert = BG_FindInvertNormalForBuildable( ent->s.modelindex );
+ }
+ else
+ minNormal = 0.707f;
+
+ // cut the velocity to keep from bouncing forever
+ if( ( trace->plane.normal[ 2 ] >= minNormal ||
+ ( invert && trace->plane.normal[ 2 ] <= -minNormal ) ) &&
+ trace->entityNum == ENTITYNUM_WORLD )
+ VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
+ else
+ VectorScale( ent->s.pos.trDelta, 0.3f, ent->s.pos.trDelta );
+
+ if( VectorLength( ent->s.pos.trDelta ) < 10 )
+ {
+ VectorMA( trace->endpos, 0.5f, trace->plane.normal, trace->endpos ); // make sure it is off ground
+ G_SetOrigin( ent, trace->endpos );
+ ent->s.groundEntityNum = trace->entityNum;
+ VectorCopy( trace->plane.normal, ent->s.origin2 );
+ VectorSet( ent->s.pos.trDelta, 0.0f, 0.0f, 0.0f );
+ return;
+ }
+
+ VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
+ VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
+ ent->s.pos.trTime = level.time;
+}
+
+#define PHYSICS_TIME 200
+
+/*
+================
+G_Physics
+
+================
+*/
+void G_Physics( gentity_t *ent, int msec )
+{
+ vec3_t origin;
+ trace_t tr;
+ int contents;
+ int mask;
+
+ // if groundentity has been set to -1, it may have been pushed off an edge
+ if( ent->s.groundEntityNum == -1 )
+ {
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ if( ent->s.pos.trType != BG_FindTrajectoryForBuildable( ent->s.modelindex ) )
+ {
+ ent->s.pos.trType = BG_FindTrajectoryForBuildable( ent->s.modelindex );
+ ent->s.pos.trTime = level.time;
+ }
+ }
+ else if( ent->s.pos.trType != TR_GRAVITY )
+ {
+ ent->s.pos.trType = TR_GRAVITY;
+ ent->s.pos.trTime = level.time;
+ }
+ }
+
+ // trace a line from the previous position to the current position
+ if( ent->clipmask )
+ mask = ent->clipmask;
+ else
+ mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
+
+ if( ent->s.pos.trType == TR_STATIONARY )
+ {
+ // check think function
+ G_RunThink( ent );
+
+ //check floor infrequently
+ if( ent->nextPhysicsTime < level.time )
+ {
+ VectorCopy( ent->r.currentOrigin, origin );
+
+ VectorMA( origin, -2.0f, ent->s.origin2, origin );
+
+ trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask );
+
+ if( tr.fraction == 1.0f )
+ ent->s.groundEntityNum = -1;
+
+ ent->nextPhysicsTime = level.time + PHYSICS_TIME;
+ }
+
+ return;
+ }
+
+ // get current position
+ BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
+
+ trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask );
+
+ VectorCopy( tr.endpos, ent->r.currentOrigin );
+
+ if( tr.startsolid )
+ tr.fraction = 0;
+
+ trap_LinkEntity( ent ); // FIXME: avoid this for stationary?
+
+ // check think function
+ G_RunThink( ent );
+
+ if( tr.fraction == 1.0f )
+ return;
+
+ // if it is in a nodrop volume, remove it
+ contents = trap_PointContents( ent->r.currentOrigin, -1 );
+ if( contents & CONTENTS_NODROP )
+ {
+ G_FreeEntity( ent );
+ return;
+ }
+
+ G_Bounce( ent, &tr );
+}
+
diff --git a/src/game/g_ptr.c b/src/game/g_ptr.c
new file mode 100644
index 0000000..e102183
--- /dev/null
+++ b/src/game/g_ptr.c
@@ -0,0 +1,143 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// g_ptr.c -- post timeout restoration handling
+
+#include "g_local.h"
+
+static connectionRecord_t connections[ MAX_CLIENTS ];
+
+/*
+===============
+G_CheckForUniquePTRC
+
+Callback to detect ptrc clashes
+===============
+*/
+static qboolean G_CheckForUniquePTRC( int code )
+{
+ int i;
+
+ if( code == 0 )
+ return qfalse;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if( connections[ i ].ptrCode == code )
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+G_UpdatePTRConnection
+
+Update the data in a connection record
+===============
+*/
+void G_UpdatePTRConnection( gclient_t *client )
+{
+ if( client && client->pers.connection )
+ {
+ client->pers.connection->clientTeam = client->pers.teamSelection;
+ client->pers.connection->clientCredit = client->pers.credit;
+ client->pers.connection->clientScore = client->pers.score;
+ }
+}
+
+/*
+===============
+G_GenerateNewConnection
+
+Generates a new connection
+===============
+*/
+connectionRecord_t *G_GenerateNewConnection( gclient_t *client )
+{
+ int code = 0;
+ int i;
+
+ // this should be really random
+ srand( trap_Milliseconds( ) );
+
+ // there is a very very small possibility that this
+ // will loop infinitely
+ do
+ {
+ code = rand( );
+ } while( !G_CheckForUniquePTRC( code ) );
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ //found an unused slot
+ if( !connections[ i ].ptrCode )
+ {
+ connections[ i ].ptrCode = code;
+ connections[ i ].clientNum = client->ps.clientNum;
+ client->pers.connection = &connections[ i ];
+ G_UpdatePTRConnection( client );
+ client->pers.connection->clientEnterTime = client->pers.enterTime;
+
+ return &connections[ i ];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+===============
+G_FindConnectionForCode
+
+Finds a connection for a given code
+===============
+*/
+connectionRecord_t *G_FindConnectionForCode( int code )
+{
+ int i;
+
+ if( code == 0 )
+ return NULL;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if( connections[ i ].ptrCode == code )
+ return &connections[ i ];
+ }
+
+ return NULL;
+}
+
+/*
+===============
+G_ResetPTRConnections
+
+Invalidate any existing codes
+===============
+*/
+void G_ResetPTRConnections( void )
+{
+ memset( connections, 0, sizeof( connectionRecord_t ) * MAX_CLIENTS );
+}
diff --git a/src/game/g_public.h b/src/game/g_public.h
new file mode 100644
index 0000000..e1e01d7
--- /dev/null
+++ b/src/game/g_public.h
@@ -0,0 +1,267 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// g_public.h -- game module information visible to server
+
+#define GAME_API_VERSION 8
+
+// entity->svFlags
+// the server does not know how to interpret most of the values
+// in entityStates (level eType), so the game must explicitly flag
+// special server behaviors
+#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects
+
+// TTimo
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=551
+#define SVF_CLIENTMASK 0x00000002
+
+#define SVF_BROADCAST 0x00000020 // send to all connected clients
+#define SVF_PORTAL 0x00000040 // merge a second pvs at origin2 into snapshots
+#define SVF_USE_CURRENT_ORIGIN 0x00000080 // entity->r.currentOrigin instead of entity->s.origin
+ // for link position (missiles and movers)
+#define SVF_SINGLECLIENT 0x00000100 // only send to a single client (entityShared_t->singleClient)
+#define SVF_NOSERVERINFO 0x00000200 // don't send CS_SERVERINFO updates to this client
+ // so that it can be updated for ping tools without
+ // lagging clients
+#define SVF_CAPSULE 0x00000400 // use capsule for collision detection instead of bbox
+#define SVF_NOTSINGLECLIENT 0x00000800 // send entity to everyone but one client
+ // (entityShared_t->singleClient)
+
+//===============================================================
+
+
+typedef struct {
+ entityState_t s; // communicated by server to clients
+
+ qboolean linked; // qfalse if not in any good cluster
+ int linkcount;
+
+ int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc
+ int singleClient; // only send to this client when SVF_SINGLECLIENT is set
+
+ qboolean bmodel; // if false, assume an explicit mins / maxs bounding box
+ // only set by trap_SetBrushModel
+ vec3_t mins, maxs;
+ int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc
+ // a non-solid entity should set to 0
+
+ vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation
+
+ // currentOrigin will be used for all collision detection and world linking.
+ // it will not necessarily be the same as the trajectory evaluation for the current
+ // time, because each entity must be moved one at a time after time is advanced
+ // to avoid simultanious collision issues
+ vec3_t currentOrigin;
+ vec3_t currentAngles;
+
+ // when a trace call is made and passEntityNum != ENTITYNUM_NONE,
+ // an ent will be excluded from testing if:
+ // ent->s.number == passEntityNum (don't interact with self)
+ // ent->s.ownerNum = passEntityNum (don't interact with your own missiles)
+ // entity[ent->s.ownerNum].ownerNum = passEntityNum (don't interact with other missiles from owner)
+ int ownerNum;
+} entityShared_t;
+
+
+
+// the server looks at a sharedEntity, which is the start of the game's gentity_t structure
+typedef struct {
+ entityState_t s; // communicated by server to clients
+ entityShared_t r; // shared by both the server system and game
+} sharedEntity_t;
+
+
+
+//===============================================================
+
+//
+// system traps provided by the main engine
+//
+typedef enum {
+ //============== general Quake services ==================
+
+ G_PRINT, // ( const char *string );
+ // print message on the local console
+
+ G_ERROR, // ( const char *string );
+ // abort the game
+
+ G_MILLISECONDS, // ( void );
+ // get current time for profiling reasons
+ // this should NOT be used for any game related tasks,
+ // because it is not journaled
+
+ // console variable interaction
+ G_CVAR_REGISTER, // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
+ G_CVAR_UPDATE, // ( vmCvar_t *vmCvar );
+ G_CVAR_SET, // ( const char *var_name, const char *value );
+ G_CVAR_VARIABLE_INTEGER_VALUE, // ( const char *var_name );
+
+ G_CVAR_VARIABLE_STRING_BUFFER, // ( const char *var_name, char *buffer, int bufsize );
+
+ G_ARGC, // ( void );
+ // ClientCommand and ServerCommand parameter access
+
+ G_ARGV, // ( int n, char *buffer, int bufferLength );
+
+ G_FS_FOPEN_FILE, // ( const char *qpath, fileHandle_t *file, fsMode_t mode );
+ G_FS_READ, // ( void *buffer, int len, fileHandle_t f );
+ G_FS_WRITE, // ( const void *buffer, int len, fileHandle_t f );
+ G_FS_FCLOSE_FILE, // ( fileHandle_t f );
+
+ G_SEND_CONSOLE_COMMAND, // ( const char *text );
+ // add commands to the console as if they were typed in
+ // for map changing, etc
+
+
+ //=========== server specific functionality =============
+
+ G_LOCATE_GAME_DATA, // ( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t,
+ // playerState_t *clients, int sizeofGameClient );
+ // the game needs to let the server system know where and how big the gentities
+ // are, so it can look at them directly without going through an interface
+
+ G_DROP_CLIENT, // ( int clientNum, const char *reason );
+ // kick a client off the server with a message
+
+ G_SEND_SERVER_COMMAND, // ( int clientNum, const char *fmt, ... );
+ // reliably sends a command string to be interpreted by the given
+ // client. If clientNum is -1, it will be sent to all clients
+
+ G_SET_CONFIGSTRING, // ( int num, const char *string );
+ // config strings hold all the index strings, and various other information
+ // that is reliably communicated to all clients
+ // All of the current configstrings are sent to clients when
+ // they connect, and changes are sent to all connected clients.
+ // All confgstrings are cleared at each level start.
+
+ G_GET_CONFIGSTRING, // ( int num, char *buffer, int bufferSize );
+
+ G_SET_CONFIGSTRING_RESTRICTIONS, // ( int num, const clientList* clientList );
+
+ G_GET_USERINFO, // ( int num, char *buffer, int bufferSize );
+ // userinfo strings are maintained by the server system, so they
+ // are persistant across level loads, while all other game visible
+ // data is completely reset
+
+ G_SET_USERINFO, // ( int num, const char *buffer );
+
+ G_GET_SERVERINFO, // ( char *buffer, int bufferSize );
+ // the serverinfo info string has all the cvars visible to server browsers
+
+ G_SET_BRUSH_MODEL, // ( gentity_t *ent, const char *name );
+ // sets mins and maxs based on the brushmodel name
+
+ G_TRACE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask );
+ // collision detection against all linked entities
+
+ G_POINT_CONTENTS, // ( const vec3_t point, int passEntityNum );
+ // point contents against all linked entities
+
+ G_IN_PVS, // ( const vec3_t p1, const vec3_t p2 );
+
+ G_IN_PVS_IGNORE_PORTALS, // ( const vec3_t p1, const vec3_t p2 );
+
+ G_ADJUST_AREA_PORTAL_STATE, // ( gentity_t *ent, qboolean open );
+
+ G_AREAS_CONNECTED, // ( int area1, int area2 );
+
+ G_LINKENTITY, // ( gentity_t *ent );
+ // an entity will never be sent to a client or used for collision
+ // if it is not passed to linkentity. If the size, position, or
+ // solidity changes, it must be relinked.
+
+ G_UNLINKENTITY, // ( gentity_t *ent );
+ // call before removing an interactive entity
+
+ G_ENTITIES_IN_BOX, // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount );
+ // EntitiesInBox will return brush models based on their bounding box,
+ // so exact determination must still be done with EntityContact
+
+ G_ENTITY_CONTACT, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent );
+ // perform an exact check against inline brush models of non-square shape
+
+ G_GET_USERCMD, // ( int clientNum, usercmd_t *cmd )
+
+ G_GET_ENTITY_TOKEN, // qboolean ( char *buffer, int bufferSize )
+ // Retrieves the next string token from the entity spawn text, returning
+ // false when all tokens have been parsed.
+ // This should only be done at GAME_INIT time.
+
+ G_FS_GETFILELIST,
+ G_REAL_TIME,
+ G_SNAPVECTOR,
+
+ G_TRACECAPSULE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask );
+ G_ENTITY_CONTACTCAPSULE, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent );
+
+ // 1.32
+ G_FS_SEEK,
+
+ G_PARSE_ADD_GLOBAL_DEFINE,
+ G_PARSE_LOAD_SOURCE,
+ G_PARSE_FREE_SOURCE,
+ G_PARSE_READ_TOKEN,
+ G_PARSE_SOURCE_FILE_AND_LINE,
+
+ G_SEND_GAMESTAT,
+
+ G_ADDCOMMAND,
+ G_REMOVECOMMAND
+} gameImport_t;
+
+
+//
+// functions exported by the game subsystem
+//
+typedef enum {
+ GAME_INIT, // ( int levelTime, int randomSeed, int restart );
+ // init and shutdown will be called every single level
+ // The game should call G_GET_ENTITY_TOKEN to parse through all the
+ // entity configuration text and spawn gentities.
+
+ GAME_SHUTDOWN, // (void);
+
+ GAME_CLIENT_CONNECT, // ( int clientNum, qboolean firstTime );
+ // return NULL if the client is allowed to connect, otherwise return
+ // a text string with the reason for denial
+
+ GAME_CLIENT_BEGIN, // ( int clientNum );
+
+ GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum );
+
+ GAME_CLIENT_DISCONNECT, // ( int clientNum );
+
+ GAME_CLIENT_COMMAND, // ( int clientNum );
+
+ GAME_CLIENT_THINK, // ( int clientNum );
+
+ GAME_RUN_FRAME, // ( int levelTime );
+
+ GAME_CONSOLE_COMMAND // ( void );
+ // ConsoleCommand will be called when a command has been issued
+ // that is not recognized as a builtin function.
+ // The game can issue trap_argc() / trap_argv() commands to get the command
+ // and parameters. Return qfalse if the game doesn't recognize it as a command.
+} gameExport_t;
+
diff --git a/src/game/g_session.c b/src/game/g_session.c
new file mode 100644
index 0000000..ef78e8a
--- /dev/null
+++ b/src/game/g_session.c
@@ -0,0 +1,171 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+
+/*
+=======================================================================
+
+ SESSION DATA
+
+Session data is the only data that stays persistant across level loads
+and tournament restarts.
+=======================================================================
+*/
+
+/*
+================
+G_WriteClientSessionData
+
+Called on game shutdown
+================
+*/
+void G_WriteClientSessionData( gclient_t *client )
+{
+ const char *s;
+ const char *var;
+
+ s = va( "%i %i %i %i %i %i %i %i %i %s",
+ client->sess.sessionTeam,
+ client->sess.restartTeam,
+ client->sess.spectatorTime,
+ client->sess.spectatorState,
+ client->sess.spectatorClient,
+ client->sess.wins,
+ client->sess.losses,
+ client->sess.teamLeader,
+ client->sess.invisible,
+ BG_ClientListString( &client->sess.ignoreList )
+ );
+
+ var = va( "session%i", client - level.clients );
+
+ trap_Cvar_Set( var, s );
+}
+
+/*
+================
+G_ReadSessionData
+
+Called on a reconnect
+================
+*/
+void G_ReadSessionData( gclient_t *client )
+{
+ char s[ MAX_STRING_CHARS ];
+ const char *var;
+
+ // bk001205 - format
+ int teamLeader;
+ int spectatorState;
+ int sessionTeam;
+ int restartTeam;
+ int invisible;
+
+ var = va( "session%i", client - level.clients );
+ trap_Cvar_VariableStringBuffer( var, s, sizeof(s) );
+
+ // FIXME: should be using BG_ClientListParse() for ignoreList, but
+ // bg_lib.c's sscanf() currently lacks %s
+ sscanf( s, "%i %i %i %i %i %i %i %i %i %x%x",
+ &sessionTeam,
+ &restartTeam,
+ &client->sess.spectatorTime,
+ &spectatorState,
+ &client->sess.spectatorClient,
+ &client->sess.wins,
+ &client->sess.losses,
+ &teamLeader,
+ &invisible,
+ &client->sess.ignoreList.hi,
+ &client->sess.ignoreList.lo
+ );
+ // bk001205 - format issues
+ client->sess.sessionTeam = (team_t)sessionTeam;
+ client->sess.restartTeam = (pTeam_t)restartTeam;
+ client->sess.spectatorState = (spectatorState_t)spectatorState;
+ client->sess.teamLeader = (qboolean)teamLeader;
+ client->sess.invisible = (qboolean)invisible;
+}
+
+
+/*
+================
+G_InitSessionData
+
+Called on a first-time connect
+================
+*/
+void G_InitSessionData( gclient_t *client, char *userinfo )
+{
+ clientSession_t *sess;
+ const char *value;
+
+ sess = &client->sess;
+
+ // initial team determination
+ value = Info_ValueForKey( userinfo, "team" );
+ if( value[ 0 ] == 's' )
+ {
+ // a willing spectator, not a waiting-in-line
+ sess->sessionTeam = TEAM_SPECTATOR;
+ }
+ else
+ {
+ if( g_maxGameClients.integer > 0 &&
+ level.numNonSpectatorClients >= g_maxGameClients.integer )
+ sess->sessionTeam = TEAM_SPECTATOR;
+ else
+ sess->sessionTeam = TEAM_FREE;
+ }
+
+ sess->restartTeam = PTE_NONE;
+ sess->spectatorState = SPECTATOR_FREE;
+ sess->spectatorTime = level.time;
+ sess->spectatorClient = -1;
+ memset( &sess->ignoreList, 0, sizeof( sess->ignoreList ) );
+
+ G_WriteClientSessionData( client );
+}
+
+
+/*
+==================
+G_WriteSessionData
+
+==================
+*/
+void G_WriteSessionData( void )
+{
+ int i;
+
+ //TA: ?
+ trap_Cvar_Set( "session", va( "%i", 0 ) );
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_CONNECTED )
+ G_WriteClientSessionData( &level.clients[ i ] );
+ }
+}
diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c
new file mode 100644
index 0000000..028c39f
--- /dev/null
+++ b/src/game/g_spawn.c
@@ -0,0 +1,698 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+qboolean G_SpawnString( const char *key, const char *defaultString, char **out )
+{
+ int i;
+
+ if( !level.spawning )
+ {
+ *out = (char *)defaultString;
+// G_Error( "G_SpawnString() called while not spawning" );
+ }
+
+ for( i = 0; i < level.numSpawnVars; i++ )
+ {
+ if( !Q_stricmp( key, level.spawnVars[ i ][ 0 ] ) )
+ {
+ *out = level.spawnVars[ i ][ 1 ];
+ return qtrue;
+ }
+ }
+
+ *out = (char *)defaultString;
+ return qfalse;
+}
+
+qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ *out = atof( s );
+ return present;
+}
+
+qboolean G_SpawnInt( const char *key, const char *defaultString, int *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ *out = atoi( s );
+ return present;
+}
+
+qboolean G_SpawnVector( const char *key, const char *defaultString, float *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ sscanf( s, "%f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ] );
+ return present;
+}
+
+qboolean G_SpawnVector4( const char *key, const char *defaultString, float *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ sscanf( s, "%f %f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ], &out[ 3 ] );
+ return present;
+}
+
+
+
+//
+// fields are needed for spawning from the entity string
+//
+typedef enum
+{
+ F_INT,
+ F_FLOAT,
+ F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL
+ F_GSTRING, // string on disk, pointer in memory, TAG_GAME
+ F_VECTOR,
+ F_VECTOR4, //TA
+ F_ANGLEHACK,
+ F_ENTITY, // index on disk, pointer in memory
+ F_ITEM, // index on disk, pointer in memory
+ F_CLIENT, // index on disk, pointer in memory
+ F_IGNORE
+} fieldtype_t;
+
+typedef struct
+{
+ char *name;
+ int ofs;
+ fieldtype_t type;
+ int flags;
+} field_t;
+
+field_t fields[ ] =
+{
+ {"classname", FOFS(classname), F_LSTRING},
+ {"origin", FOFS(s.origin), F_VECTOR},
+ {"model", FOFS(model), F_LSTRING},
+ {"model2", FOFS(model2), F_LSTRING},
+ {"spawnflags", FOFS(spawnflags), F_INT},
+ {"speed", FOFS(speed), F_FLOAT},
+ {"target", FOFS(target), F_LSTRING},
+ {"targetname", FOFS(targetname), F_LSTRING},
+ {"message", FOFS(message), F_LSTRING},
+ {"team", FOFS(team), F_LSTRING},
+ {"wait", FOFS(wait), F_FLOAT},
+ {"random", FOFS(random), F_FLOAT},
+ {"count", FOFS(count), F_INT},
+ {"health", FOFS(health), F_INT},
+ {"light", 0, F_IGNORE},
+ {"dmg", FOFS(damage), F_INT},
+ {"angles", FOFS(s.angles), F_VECTOR},
+ {"angle", FOFS(s.angles), F_ANGLEHACK},
+ {"bounce", FOFS(physicsBounce), F_FLOAT},
+ {"alpha", FOFS(pos1), F_VECTOR},
+ {"radius", FOFS(pos2), F_VECTOR},
+ {"acceleration", FOFS(acceleration), F_VECTOR},
+ {"animation", FOFS(animation), F_VECTOR4},
+ {"rotatorAngle", FOFS(rotatorAngle), F_FLOAT},
+ {"targetShaderName", FOFS(targetShaderName), F_LSTRING},
+ {"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING},
+
+ {NULL}
+};
+
+
+typedef struct
+{
+ char *name;
+ void (*spawn)(gentity_t *ent);
+} spawn_t;
+
+void SP_info_player_start( gentity_t *ent );
+void SP_info_player_deathmatch( gentity_t *ent );
+void SP_info_player_intermission( gentity_t *ent );
+
+void SP_info_alien_intermission( gentity_t *ent );
+void SP_info_human_intermission( gentity_t *ent );
+
+void SP_info_firstplace( gentity_t *ent );
+void SP_info_secondplace( gentity_t *ent );
+void SP_info_thirdplace( gentity_t *ent );
+void SP_info_podium( gentity_t *ent );
+
+void SP_func_plat( gentity_t *ent );
+void SP_func_static( gentity_t *ent );
+void SP_func_rotating( gentity_t *ent );
+void SP_func_bobbing( gentity_t *ent );
+void SP_func_pendulum( gentity_t *ent );
+void SP_func_button( gentity_t *ent );
+void SP_func_door( gentity_t *ent );
+void SP_func_door_rotating( gentity_t *ent );
+void SP_func_door_model( gentity_t *ent );
+void SP_func_train( gentity_t *ent );
+void SP_func_timer( gentity_t *self);
+
+void SP_trigger_always( gentity_t *ent );
+void SP_trigger_multiple( gentity_t *ent );
+void SP_trigger_push( gentity_t *ent );
+void SP_trigger_teleport( gentity_t *ent );
+void SP_trigger_hurt( gentity_t *ent );
+void SP_trigger_stage( gentity_t *ent );
+void SP_trigger_win( gentity_t *ent );
+void SP_trigger_buildable( gentity_t *ent );
+void SP_trigger_class( gentity_t *ent );
+void SP_trigger_equipment( gentity_t *ent );
+void SP_trigger_gravity( gentity_t *ent );
+void SP_trigger_heal( gentity_t *ent );
+void SP_trigger_ammo( gentity_t *ent );
+
+void SP_target_delay( gentity_t *ent );
+void SP_target_speaker( gentity_t *ent );
+void SP_target_print( gentity_t *ent );
+void SP_target_character( gentity_t *ent );
+void SP_target_score( gentity_t *ent );
+void SP_target_teleporter( gentity_t *ent );
+void SP_target_relay( gentity_t *ent );
+void SP_target_kill( gentity_t *ent );
+void SP_target_position( gentity_t *ent );
+void SP_target_location( gentity_t *ent );
+void SP_target_push( gentity_t *ent );
+void SP_target_rumble( gentity_t *ent );
+void SP_target_alien_win( gentity_t *ent );
+void SP_target_human_win( gentity_t *ent );
+void SP_target_hurt( gentity_t *ent );
+
+void SP_light( gentity_t *self );
+void SP_info_null( gentity_t *self );
+void SP_info_notnull( gentity_t *self );
+void SP_info_camp( gentity_t *self );
+void SP_path_corner( gentity_t *self );
+
+void SP_misc_teleporter_dest( gentity_t *self );
+void SP_misc_model( gentity_t *ent );
+void SP_misc_portal_camera( gentity_t *ent );
+void SP_misc_portal_surface( gentity_t *ent );
+
+void SP_shooter_rocket( gentity_t *ent );
+void SP_shooter_plasma( gentity_t *ent );
+void SP_shooter_grenade( gentity_t *ent );
+
+void SP_misc_particle_system( gentity_t *ent );
+void SP_misc_anim_model( gentity_t *ent );
+void SP_misc_light_flare( gentity_t *ent );
+
+spawn_t spawns[ ] =
+{
+ // info entities don't do anything at all, but provide positional
+ // information for things controlled by other processes
+ { "info_player_start", SP_info_player_start },
+ { "info_player_deathmatch", SP_info_player_deathmatch },
+ { "info_player_intermission", SP_info_player_intermission },
+
+ //TA: extra bits
+ { "info_alien_intermission", SP_info_alien_intermission },
+ { "info_human_intermission", SP_info_human_intermission },
+
+ { "info_null", SP_info_null },
+ { "info_notnull", SP_info_notnull }, // use target_position instead
+
+ { "func_plat", SP_func_plat },
+ { "func_button", SP_func_button },
+ { "func_door", SP_func_door },
+ { "func_door_rotating", SP_func_door_rotating }, //TA
+ { "func_door_model", SP_func_door_model }, //TA
+ { "func_static", SP_func_static },
+ { "func_rotating", SP_func_rotating },
+ { "func_bobbing", SP_func_bobbing },
+ { "func_pendulum", SP_func_pendulum },
+ { "func_train", SP_func_train },
+ { "func_group", SP_info_null },
+ { "func_timer", SP_func_timer }, // rename trigger_timer?
+
+ // Triggers are brush objects that cause an effect when contacted
+ // by a living player, usually involving firing targets.
+ // While almost everything could be done with
+ // a single trigger class and different targets, triggered effects
+ // could not be client side predicted (push and teleport).
+ { "trigger_always", SP_trigger_always },
+ { "trigger_multiple", SP_trigger_multiple },
+ { "trigger_push", SP_trigger_push },
+ { "trigger_teleport", SP_trigger_teleport },
+ { "trigger_hurt", SP_trigger_hurt },
+ { "trigger_stage", SP_trigger_stage },
+ { "trigger_win", SP_trigger_win },
+ { "trigger_buildable", SP_trigger_buildable },
+ { "trigger_class", SP_trigger_class },
+ { "trigger_equipment", SP_trigger_equipment },
+ { "trigger_gravity", SP_trigger_gravity },
+ { "trigger_heal", SP_trigger_heal },
+ { "trigger_ammo", SP_trigger_ammo },
+
+ // targets perform no action by themselves, but must be triggered
+ // by another entity
+ { "target_delay", SP_target_delay },
+ { "target_speaker", SP_target_speaker },
+ { "target_print", SP_target_print },
+ { "target_score", SP_target_score },
+ { "target_teleporter", SP_target_teleporter },
+ { "target_relay", SP_target_relay },
+ { "target_kill", SP_target_kill },
+ { "target_position", SP_target_position },
+ { "target_location", SP_target_location },
+ { "target_push", SP_target_push },
+ { "target_rumble", SP_target_rumble },
+ { "target_alien_win", SP_target_alien_win },
+ { "target_human_win", SP_target_human_win },
+ { "target_hurt", SP_target_hurt },
+
+ { "light", SP_light },
+ { "path_corner", SP_path_corner },
+
+ { "misc_teleporter_dest", SP_misc_teleporter_dest },
+ { "misc_model", SP_misc_model },
+ { "misc_portal_surface", SP_misc_portal_surface },
+ { "misc_portal_camera", SP_misc_portal_camera },
+
+ { "misc_particle_system", SP_misc_particle_system },
+ { "misc_anim_model", SP_misc_anim_model },
+ { "misc_light_flare", SP_misc_light_flare },
+
+ { NULL, 0 }
+};
+
+/*
+===============
+G_CallSpawn
+
+Finds the spawn function for the entity and calls it,
+returning qfalse if not found
+===============
+*/
+qboolean G_CallSpawn( gentity_t *ent )
+{
+ spawn_t *s;
+ buildable_t buildable;
+
+ if( !ent->classname )
+ {
+ G_Printf( "G_CallSpawn: NULL classname\n" );
+ return qfalse;
+ }
+
+ //check buildable spawn functions
+ if( ( buildable = BG_FindBuildNumForEntityName( ent->classname ) ) != BA_NONE )
+ {
+ // don't spawn built-in buildings if we are using a custom layout
+ if( level.layout[ 0 ] && Q_stricmp( level.layout, "*BUILTIN*" ) )
+ return qtrue;
+
+ if( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN )
+ {
+ ent->s.angles[ YAW ] += 180.0f;
+ AngleNormalize360( ent->s.angles[ YAW ] );
+ }
+
+ G_SpawnBuildable( ent, buildable );
+ return qtrue;
+ }
+
+ // check normal spawn functions
+ for( s = spawns; s->name; s++ )
+ {
+ if( !strcmp( s->name, ent->classname ) )
+ {
+ // found it
+ s->spawn( ent );
+ return qtrue;
+ }
+ }
+
+ G_Printf( "%s doesn't have a spawn function\n", ent->classname );
+ return qfalse;
+}
+
+/*
+=============
+G_NewString
+
+Builds a copy of the string, translating \n to real linefeeds
+so message texts can be multi-line
+=============
+*/
+char *G_NewString( const char *string )
+{
+ char *newb, *new_p;
+ int i,l;
+
+ l = strlen( string ) + 1;
+
+ newb = G_Alloc( l );
+
+ new_p = newb;
+
+ // turn \n into a real linefeed
+ for( i = 0 ; i < l ; i++ )
+ {
+ if( string[ i ] == '\\' && i < l - 1 )
+ {
+ i++;
+ if( string[ i ] == 'n' )
+ *new_p++ = '\n';
+ else
+ *new_p++ = '\\';
+ }
+ else
+ *new_p++ = string[ i ];
+ }
+
+ return newb;
+}
+
+
+
+
+/*
+===============
+G_ParseField
+
+Takes a key/value pair and sets the binary values
+in a gentity
+===============
+*/
+void G_ParseField( const char *key, const char *value, gentity_t *ent )
+{
+ field_t *f;
+ byte *b;
+ float v;
+ vec3_t vec;
+ vec4_t vec4;
+
+ for( f = fields; f->name; f++ )
+ {
+ if( !Q_stricmp( f->name, key ) )
+ {
+ // found it
+ b = (byte *)ent;
+
+ switch( f->type )
+ {
+ case F_LSTRING:
+ *(char **)( b + f->ofs ) = G_NewString( value );
+ break;
+
+ case F_VECTOR:
+ sscanf( value, "%f %f %f", &vec[ 0 ], &vec[ 1 ], &vec[ 2 ] );
+
+ ( (float *)( b + f->ofs ) )[ 0 ] = vec[ 0 ];
+ ( (float *)( b + f->ofs ) )[ 1 ] = vec[ 1 ];
+ ( (float *)( b + f->ofs ) )[ 2 ] = vec[ 2 ];
+ break;
+
+ case F_VECTOR4:
+ sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] );
+
+ ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ];
+ ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ];
+ ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ];
+ ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ];
+ break;
+
+ case F_INT:
+ *(int *)( b + f->ofs ) = atoi( value );
+ break;
+
+ case F_FLOAT:
+ *(float *)( b + f->ofs ) = atof( value );
+ break;
+
+ case F_ANGLEHACK:
+ v = atof( value );
+ ( (float *)( b + f->ofs ) )[ 0 ] = 0;
+ ( (float *)( b + f->ofs ) )[ 1 ] = v;
+ ( (float *)( b + f->ofs ) )[ 2 ] = 0;
+ break;
+
+ default:
+ case F_IGNORE:
+ break;
+ }
+
+ return;
+ }
+ }
+}
+
+
+
+
+/*
+===================
+G_SpawnGEntityFromSpawnVars
+
+Spawn an entity and fill in all of the level fields from
+level.spawnVars[], then call the class specfic spawn function
+===================
+*/
+void G_SpawnGEntityFromSpawnVars( void )
+{
+ int i;
+ gentity_t *ent;
+
+ // get the next free entity
+ ent = G_Spawn( );
+
+ for( i = 0 ; i < level.numSpawnVars ; i++ )
+ G_ParseField( level.spawnVars[ i ][ 0 ], level.spawnVars[ i ][ 1 ], ent );
+
+ G_SpawnInt( "notq3a", "0", &i );
+
+ if( i )
+ {
+ G_FreeEntity( ent );
+ return;
+ }
+
+ // move editor origin to pos
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ VectorCopy( ent->s.origin, ent->r.currentOrigin );
+
+ // if we didn't get a classname, don't bother spawning anything
+ if( !G_CallSpawn( ent ) )
+ G_FreeEntity( ent );
+}
+
+
+
+/*
+====================
+G_AddSpawnVarToken
+====================
+*/
+char *G_AddSpawnVarToken( const char *string )
+{
+ int l;
+ char *dest;
+
+ l = strlen( string );
+ if( level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS )
+ G_Error( "G_AddSpawnVarToken: MAX_SPAWN_CHARS" );
+
+ dest = level.spawnVarChars + level.numSpawnVarChars;
+ memcpy( dest, string, l + 1 );
+
+ level.numSpawnVarChars += l + 1;
+
+ return dest;
+}
+
+/*
+====================
+G_ParseSpawnVars
+
+Parses a brace bounded set of key / value pairs out of the
+level's entity strings into level.spawnVars[]
+
+This does not actually spawn an entity.
+====================
+*/
+qboolean G_ParseSpawnVars( void )
+{
+ char keyname[ MAX_TOKEN_CHARS ];
+ char com_token[ MAX_TOKEN_CHARS ];
+
+ level.numSpawnVars = 0;
+ level.numSpawnVarChars = 0;
+
+ // parse the opening brace
+ if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) )
+ {
+ // end of spawn string
+ return qfalse;
+ }
+
+ if( com_token[ 0 ] != '{' )
+ G_Error( "G_ParseSpawnVars: found %s when expecting {", com_token );
+
+ // go through all the key / value pairs
+ while( 1 )
+ {
+ // parse key
+ if( !trap_GetEntityToken( keyname, sizeof( keyname ) ) )
+ G_Error( "G_ParseSpawnVars: EOF without closing brace" );
+
+ if( keyname[0] == '}' )
+ break;
+
+ // parse value
+ if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) )
+ G_Error( "G_ParseSpawnVars: EOF without closing brace" );
+
+ if( com_token[0] == '}' )
+ G_Error( "G_ParseSpawnVars: closing brace without data" );
+
+ if( level.numSpawnVars == MAX_SPAWN_VARS )
+ G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" );
+
+ level.spawnVars[ level.numSpawnVars ][ 0 ] = G_AddSpawnVarToken( keyname );
+ level.spawnVars[ level.numSpawnVars ][ 1 ] = G_AddSpawnVarToken( com_token );
+ level.numSpawnVars++;
+ }
+
+ return qtrue;
+}
+
+
+
+/*QUAKED worldspawn (0 0 0) ?
+
+Every map should have exactly one worldspawn.
+"music" music wav file
+"gravity" 800 is default gravity
+"message" Text to print during connection process
+*/
+void SP_worldspawn( void )
+{
+ char *s;
+
+ G_SpawnString( "classname", "", &s );
+
+ if( Q_stricmp( s, "worldspawn" ) )
+ G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" );
+
+ // make some data visible to connecting client
+ trap_SetConfigstring( CS_GAME_VERSION, GAME_VERSION );
+
+ trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) );
+
+ G_SpawnString( "music", "", &s );
+ trap_SetConfigstring( CS_MUSIC, s );
+
+ G_SpawnString( "message", "", &s );
+ trap_SetConfigstring( CS_MESSAGE, s ); // map specific message
+
+ trap_SetConfigstring( CS_MOTD, g_motd.string ); // message of the day
+
+ G_SpawnString( "gravity", "800", &s );
+ trap_Cvar_Set( "g_gravity", s );
+
+ G_SpawnString( "humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, &s );
+ trap_Cvar_Set( "g_humanBuildPoints", s );
+
+ G_SpawnString( "humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, &s );
+ trap_Cvar_Set( "g_humanMaxStage", s );
+
+ G_SpawnString( "humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, &s );
+ trap_Cvar_Set( "g_humanStage2Threshold", s );
+
+ G_SpawnString( "humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, &s );
+ trap_Cvar_Set( "g_humanStage3Threshold", s );
+
+ G_SpawnString( "alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, &s );
+ trap_Cvar_Set( "g_alienBuildPoints", s );
+
+ G_SpawnString( "alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, &s );
+ trap_Cvar_Set( "g_alienMaxStage", s );
+
+ G_SpawnString( "alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, &s );
+ trap_Cvar_Set( "g_alienStage2Threshold", s );
+
+ G_SpawnString( "alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, &s );
+ trap_Cvar_Set( "g_alienStage3Threshold", s );
+
+ G_SpawnString( "enableDust", "0", &s );
+ trap_Cvar_Set( "g_enableDust", s );
+
+ G_SpawnString( "enableBreath", "0", &s );
+ trap_Cvar_Set( "g_enableBreath", s );
+
+ G_SpawnString( "disabledEquipment", "", &s );
+ trap_Cvar_Set( "g_disabledEquipment", s );
+
+ G_SpawnString( "disabledClasses", "", &s );
+ trap_Cvar_Set( "g_disabledClasses", s );
+
+ G_SpawnString( "disabledBuildables", "", &s );
+ trap_Cvar_Set( "g_disabledBuildables", s );
+
+ g_entities[ ENTITYNUM_WORLD ].s.number = ENTITYNUM_WORLD;
+ g_entities[ ENTITYNUM_WORLD ].classname = "worldspawn";
+
+ if( g_restarted.integer )
+ trap_Cvar_Set( "g_restarted", "0" );
+
+}
+
+
+/*
+==============
+G_SpawnEntitiesFromString
+
+Parses textual entity definitions out of an entstring and spawns gentities.
+==============
+*/
+void G_SpawnEntitiesFromString( void )
+{
+ // allow calls to G_Spawn*()
+ level.spawning = qtrue;
+ level.numSpawnVars = 0;
+
+ // the worldspawn is not an actual entity, but it still
+ // has a "spawn" function to perform any global setup
+ // needed by a level (setting configstrings or cvars, etc)
+ if( !G_ParseSpawnVars( ) )
+ G_Error( "SpawnEntities: no entities" );
+
+ SP_worldspawn( );
+
+ // parse ents
+ while( G_ParseSpawnVars( ) )
+ G_SpawnGEntityFromSpawnVars( );
+
+ level.spawning = qfalse; // any future calls to G_Spawn*() will be errors
+}
+
diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c
new file mode 100644
index 0000000..62148c8
--- /dev/null
+++ b/src/game/g_svcmds.c
@@ -0,0 +1,751 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// this file holds commands that can be executed by the server console, but not remote clients
+
+#include "g_local.h"
+
+
+/*
+==============================================================================
+
+PACKET FILTERING
+
+
+You can add or remove addresses from the filter list with:
+
+addip <ip>
+removeip <ip>
+
+The ip address is specified in dot format, and you can use '*' to match any value
+so you can specify an entire class C network with "addip 192.246.40.*"
+
+Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host.
+
+listip
+Prints the current list of filters.
+
+g_filterban <0 or 1>
+
+If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting.
+
+If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network.
+
+TTimo NOTE: for persistence, bans are stored in g_banIPs cvar MAX_CVAR_VALUE_STRING
+The size of the cvar string buffer is limiting the banning to around 20 masks
+this could be improved by putting some g_banIPs2 g_banIps3 etc. maybe
+still, you should rely on PB for banning instead
+
+==============================================================================
+*/
+
+// extern vmCvar_t g_banIPs;
+// extern vmCvar_t g_filterBan;
+
+
+typedef struct ipFilter_s
+{
+ unsigned mask;
+ unsigned compare;
+} ipFilter_t;
+
+#define MAX_IPFILTERS 1024
+
+static ipFilter_t ipFilters[ MAX_IPFILTERS ];
+static int numIPFilters;
+
+/*
+=================
+StringToFilter
+=================
+*/
+static qboolean StringToFilter( char *s, ipFilter_t *f )
+{
+ char num[ 128 ];
+ int i, j;
+ byte b[ 4 ];
+ byte m[ 4 ];
+
+ for( i = 0; i < 4; i++ )
+ {
+ b[ i ] = 0;
+ m[ i ] = 0;
+ }
+
+ for( i = 0; i < 4; i++ )
+ {
+ if( *s < '0' || *s > '9' )
+ {
+ if( *s == '*' ) // 'match any'
+ {
+ //b[ i ] and m[ i ] to 0
+ s++;
+ if ( !*s )
+ break;
+
+ s++;
+ continue;
+ }
+
+ G_Printf( "Bad filter address: %s\n", s );
+ return qfalse;
+ }
+
+ j = 0;
+ while( *s >= '0' && *s <= '9' )
+ num[ j++ ] = *s++;
+
+ num[ j ] = 0;
+ b[ i ] = atoi( num );
+
+ m[ i ] = 255;
+
+ if( !*s )
+ break;
+
+ s++;
+ }
+
+ f->mask = *(unsigned *)m;
+ f->compare = *(unsigned *)b;
+
+ return qtrue;
+}
+
+/*
+=================
+UpdateIPBans
+=================
+*/
+static void UpdateIPBans( void )
+{
+ byte b[ 4 ];
+ byte m[ 4 ];
+ int i, j;
+ char iplist_final[ MAX_CVAR_VALUE_STRING ];
+ char ip[ 64 ];
+
+ *iplist_final = 0;
+
+ for( i = 0 ; i < numIPFilters ; i++ )
+ {
+ if( ipFilters[ i ].compare == 0xffffffff )
+ continue;
+
+ *(unsigned *)b = ipFilters[ i ].compare;
+ *(unsigned *)m = ipFilters[ i ].mask;
+ *ip = 0;
+
+ for( j = 0 ; j < 4 ; j++ )
+ {
+ if( m[ j ] != 255 )
+ Q_strcat( ip, sizeof( ip ), "*" );
+ else
+ Q_strcat( ip, sizeof( ip ), va( "%i", b[ j ] ) );
+
+ Q_strcat( ip, sizeof( ip ), ( j < 3 ) ? "." : " " );
+ }
+
+ if( strlen( iplist_final ) + strlen( ip ) < MAX_CVAR_VALUE_STRING )
+ Q_strcat( iplist_final, sizeof( iplist_final ), ip );
+ else
+ {
+ Com_Printf( "g_banIPs overflowed at MAX_CVAR_VALUE_STRING\n" );
+ break;
+ }
+ }
+
+ trap_Cvar_Set( "g_banIPs", iplist_final );
+}
+
+/*
+=================
+G_FilterPacket
+=================
+*/
+qboolean G_FilterPacket( char *from )
+{
+ int i;
+ unsigned in;
+ byte m[ 4 ];
+ char *p;
+
+ i = 0;
+ p = from;
+ while( *p && i < 4 )
+ {
+ m[ i ] = 0;
+ while( *p >= '0' && *p <= '9' )
+ {
+ m[ i ] = m[ i ] * 10 + ( *p - '0' );
+ p++;
+ }
+
+ if( !*p || *p == ':' )
+ break;
+
+ i++, p++;
+ }
+
+ in = *(unsigned *)m;
+
+ for( i = 0; i < numIPFilters; i++ )
+ if( ( in & ipFilters[ i ].mask ) == ipFilters[ i ].compare )
+ return g_filterBan.integer != 0;
+
+ return g_filterBan.integer == 0;
+}
+
+/*
+=================
+AddIP
+=================
+*/
+static void AddIP( char *str )
+{
+ int i;
+
+ for( i = 0 ; i < numIPFilters ; i++ )
+ if( ipFilters[ i ].compare == 0xffffffff )
+ break; // free spot
+
+ if( i == numIPFilters )
+ {
+ if( numIPFilters == MAX_IPFILTERS )
+ {
+ G_Printf( "IP filter list is full\n" );
+ return;
+ }
+
+ numIPFilters++;
+ }
+
+ if( !StringToFilter( str, &ipFilters[ i ] ) )
+ ipFilters[ i ].compare = 0xffffffffu;
+
+ UpdateIPBans( );
+}
+
+/*
+=================
+G_ProcessIPBans
+=================
+*/
+void G_ProcessIPBans( void )
+{
+ char *s, *t;
+ char str[ MAX_CVAR_VALUE_STRING ];
+
+ Q_strncpyz( str, g_banIPs.string, sizeof( str ) );
+
+ for( t = s = g_banIPs.string; *t; /* */ )
+ {
+ s = strchr( s, ' ' );
+
+ if( !s )
+ break;
+
+ while( *s == ' ' )
+ *s++ = 0;
+
+ if( *t )
+ AddIP( t );
+
+ t = s;
+ }
+}
+
+
+/*
+=================
+Svcmd_AddIP_f
+=================
+*/
+void Svcmd_AddIP_f( void )
+{
+ char str[ MAX_TOKEN_CHARS ];
+
+ if( trap_Argc( ) < 2 )
+ {
+ G_Printf( "Usage: addip <ip-mask>\n" );
+ return;
+ }
+
+ trap_Argv( 1, str, sizeof( str ) );
+
+ AddIP( str );
+}
+
+/*
+=================
+Svcmd_RemoveIP_f
+=================
+*/
+void Svcmd_RemoveIP_f( void )
+{
+ ipFilter_t f;
+ int i;
+ char str[ MAX_TOKEN_CHARS ];
+
+ if( trap_Argc( ) < 2 )
+ {
+ G_Printf( "Usage: sv removeip <ip-mask>\n" );
+ return;
+ }
+
+ trap_Argv( 1, str, sizeof( str ) );
+
+ if( !StringToFilter( str, &f ) )
+ return;
+
+ for( i = 0; i < numIPFilters; i++ )
+ {
+ if( ipFilters[ i ].mask == f.mask &&
+ ipFilters[ i ].compare == f.compare)
+ {
+ ipFilters[ i ].compare = 0xffffffffu;
+ G_Printf ( "Removed.\n" );
+
+ UpdateIPBans( );
+ return;
+ }
+ }
+
+ G_Printf ( "Didn't find %s.\n", str );
+}
+
+/*
+===================
+Svcmd_EntityList_f
+===================
+*/
+void Svcmd_EntityList_f( void )
+{
+ int e;
+ gentity_t *check;
+
+ check = g_entities;
+
+ for( e = 0; e < level.num_entities; e++, check++ )
+ {
+ if( !check->inuse )
+ continue;
+
+ G_Printf( "%3i:", e );
+
+ switch( check->s.eType )
+ {
+ case ET_GENERAL:
+ G_Printf( "ET_GENERAL " );
+ break;
+ case ET_PLAYER:
+ G_Printf( "ET_PLAYER " );
+ break;
+ case ET_ITEM:
+ G_Printf( "ET_ITEM " );
+ break;
+ case ET_BUILDABLE:
+ G_Printf( "ET_BUILDABLE " );
+ break;
+ case ET_MISSILE:
+ G_Printf( "ET_MISSILE " );
+ break;
+ case ET_MOVER:
+ G_Printf( "ET_MOVER " );
+ break;
+ case ET_BEAM:
+ G_Printf( "ET_BEAM " );
+ break;
+ case ET_PORTAL:
+ G_Printf( "ET_PORTAL " );
+ break;
+ case ET_SPEAKER:
+ G_Printf( "ET_SPEAKER " );
+ break;
+ case ET_PUSH_TRIGGER:
+ G_Printf( "ET_PUSH_TRIGGER " );
+ break;
+ case ET_TELEPORT_TRIGGER:
+ G_Printf( "ET_TELEPORT_TRIGGER " );
+ break;
+ case ET_INVISIBLE:
+ G_Printf( "ET_INVISIBLE " );
+ break;
+ case ET_GRAPPLE:
+ G_Printf( "ET_GRAPPLE " );
+ break;
+ default:
+ G_Printf( "%3i ", check->s.eType );
+ break;
+ }
+
+ if( check->classname )
+ G_Printf( "%s", check->classname );
+
+ G_Printf( "\n" );
+ }
+}
+
+gclient_t *ClientForString( const char *s )
+{
+ gclient_t *cl;
+ int i;
+ int idnum;
+
+ // numeric values are just slot numbers
+ if( s[ 0 ] >= '0' && s[ 0 ] <= '9' )
+ {
+ idnum = atoi( s );
+
+ if( idnum < 0 || idnum >= level.maxclients )
+ {
+ Com_Printf( "Bad client slot: %i\n", idnum );
+ return NULL;
+ }
+
+ cl = &level.clients[ idnum ];
+
+ if( cl->pers.connected == CON_DISCONNECTED )
+ {
+ G_Printf( "Client %i is not connected\n", idnum );
+ return NULL;
+ }
+
+ return cl;
+ }
+
+ // check for a name match
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ cl = &level.clients[ i ];
+ if( cl->pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( !Q_stricmp( cl->pers.netname, s ) )
+ return cl;
+ }
+
+ G_Printf( "User %s is not on the server\n", s );
+
+ return NULL;
+}
+
+/*
+===================
+Svcmd_ForceTeam_f
+
+forceteam <player> <team>
+===================
+*/
+void Svcmd_ForceTeam_f( void )
+{
+ gclient_t *cl;
+ char str[ MAX_TOKEN_CHARS ];
+
+ // find the player
+ trap_Argv( 1, str, sizeof( str ) );
+ cl = ClientForString( str );
+
+ if( !cl )
+ return;
+
+ // set the team
+ trap_Argv( 2, str, sizeof( str ) );
+ /*SetTeam( &g_entities[cl - level.clients], str );*/
+ //FIXME: tremulise this
+}
+
+/*
+===================
+Svcmd_LayoutSave_f
+
+layoutsave <name>
+===================
+*/
+void Svcmd_LayoutSave_f( void )
+{
+ char str[ MAX_QPATH ];
+ char str2[ MAX_QPATH - 4 ];
+ char *s;
+ int i = 0;
+
+ if( trap_Argc( ) != 2 )
+ {
+ G_Printf( "usage: layoutsave LAYOUTNAME\n" );
+ return;
+ }
+ trap_Argv( 1, str, sizeof( str ) );
+
+ // sanitize name
+ s = &str[ 0 ];
+ while( *s && i < sizeof( str2 ) - 1 )
+ {
+ if( ( *s >= '0' && *s <= '9' ) ||
+ ( *s >= 'a' && *s <= 'z' ) ||
+ ( *s >= 'A' && *s <= 'Z' ) || *s == '-' || *s == '_' )
+ {
+ str2[ i++ ] = *s;
+ str2[ i ] = '\0';
+ }
+ s++;
+ }
+
+ if( !str2[ 0 ] )
+ {
+ G_Printf("layoutsave: invalid name \"%s\"\n", str );
+ return;
+ }
+
+ G_LayoutSave( str2 );
+}
+
+char *ConcatArgs( int start );
+
+/*
+===================
+Svcmd_LayoutLoad_f
+
+layoutload [<name> [<name2> [<name3 [...]]]]
+
+This is just a silly alias for doing:
+ set g_layouts "name name2 name3"
+ map_restart
+===================
+*/
+void Svcmd_LayoutLoad_f( void )
+{
+ char layouts[ MAX_CVAR_VALUE_STRING ];
+ char *s;
+
+ s = ConcatArgs( 1 );
+ Q_strncpyz( layouts, s, sizeof( layouts ) );
+ trap_Cvar_Set( "g_layouts", layouts );
+ trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" );
+ level.restarted = qtrue;
+}
+
+static void Svcmd_AdmitDefeat_f( void )
+{
+ int team;
+ char teamNum[ 2 ];
+
+ if( trap_Argc( ) != 2 )
+ {
+ G_Printf("admitdefeat: must provide a team\n");
+ return;
+ }
+ trap_Argv( 1, teamNum, sizeof( teamNum ) );
+ team = atoi( teamNum );
+ if( team == PTE_ALIENS || teamNum[ 0 ] == 'a' )
+ {
+ level.surrenderTeam = PTE_ALIENS;
+ G_BaseSelfDestruct( PTE_ALIENS );
+ G_TeamCommand( PTE_ALIENS, "cp \"Hivemind Link Broken\" 1");
+ trap_SendServerCommand( -1, "print \"Alien team has admitted defeat\n\"" );
+ }
+ else if( team == PTE_HUMANS || teamNum[ 0 ] == 'h' )
+ {
+ level.surrenderTeam = PTE_HUMANS;
+ G_BaseSelfDestruct( PTE_HUMANS );
+ G_TeamCommand( PTE_HUMANS, "cp \"Life Support Terminated\" 1");
+ trap_SendServerCommand( -1, "print \"Human team has admitted defeat\n\"" );
+ }
+ else
+ {
+ G_Printf("admitdefeat: invalid team\n");
+ }
+}
+
+/*
+=================
+ConsoleCommand
+
+=================
+*/
+qboolean ConsoleCommand( void )
+{
+ char cmd[ MAX_TOKEN_CHARS ];
+
+ trap_Argv( 0, cmd, sizeof( cmd ) );
+
+ if( Q_stricmp( cmd, "entitylist" ) == 0 )
+ {
+ Svcmd_EntityList_f( );
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "forceteam" ) == 0 )
+ {
+ Svcmd_ForceTeam_f( );
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "game_memory" ) == 0 )
+ {
+ Svcmd_GameMem_f( );
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "addip" ) == 0 )
+ {
+ Svcmd_AddIP_f( );
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "removeip" ) == 0 )
+ {
+ Svcmd_RemoveIP_f( );
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "listip" ) == 0 )
+ {
+ trap_SendConsoleCommand( EXEC_NOW, "g_banIPs\n" );
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "mapRotation" ) == 0 )
+ {
+ char *rotationName = ConcatArgs( 1 );
+
+ if( !G_StartMapRotation( rotationName, qfalse ) )
+ G_Printf( "Can't find map rotation %s\n", rotationName );
+
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "stopMapRotation" ) == 0 )
+ {
+ G_StopMapRotation( );
+
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "advanceMapRotation" ) == 0 )
+ {
+ G_AdvanceMapRotation( );
+
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "alienWin" ) == 0 )
+ {
+ int i;
+ gentity_t *e;
+
+ for( i = 1, e = g_entities + i; i < level.num_entities; i++, e++ )
+ {
+ if( e->s.modelindex == BA_H_SPAWN )
+ G_Damage( e, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ }
+
+ return qtrue;
+ }
+
+ if( Q_stricmp( cmd, "humanWin" ) == 0 )
+ {
+ int i;
+ gentity_t *e;
+
+ for( i = 1, e = g_entities + i; i < level.num_entities; i++, e++ )
+ {
+ if( e->s.modelindex == BA_A_SPAWN )
+ G_Damage( e, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ }
+
+ return qtrue;
+ }
+
+ if( !Q_stricmp( cmd, "layoutsave" ) )
+ {
+ Svcmd_LayoutSave_f( );
+ return qtrue;
+ }
+
+ if( !Q_stricmp( cmd, "layoutload" ) )
+ {
+ Svcmd_LayoutLoad_f( );
+ return qtrue;
+ }
+
+ if( !Q_stricmp( cmd, "admitdefeat" ) )
+ {
+ Svcmd_AdmitDefeat_f( );
+ return qtrue;
+ }
+
+ if( !Q_stricmp( cmd, "evacuation" ) )
+ {
+ trap_SendServerCommand( -1, "print \"Evacuation ordered\n\"" );
+ level.lastWin = PTE_NONE;
+ trap_SetConfigstring( CS_WINNER, "Evacuation" );
+ LogExit( "Evacuation." );
+ G_admin_maplog_result( "d" );
+ return qtrue;
+ }
+
+ // see if this is a a admin command
+ if( G_admin_cmd_check( NULL, qfalse ) )
+ return qtrue;
+
+ if( g_dedicated.integer )
+ {
+ if( Q_stricmp( cmd, "say" ) == 0 )
+ {
+ trap_SendServerCommand( -1, va( "print \"server: %s\n\"", ConcatArgs( 1 ) ) );
+ return qtrue;
+ }
+ else if( !Q_stricmp( cmd, "chat" ) )
+ {
+ trap_SendServerCommand( -1, va( "chat \"%s\" -1 0", ConcatArgs( 1 ) ) );
+ G_Printf( "chat: %s\n", ConcatArgs( 1 ) );
+ return qtrue;
+ }
+ else if( !Q_stricmp( cmd, "cp" ) )
+ {
+ G_CP( NULL );
+ return qtrue;
+ }
+ else if( !Q_stricmp( cmd, "m" ) )
+ {
+ G_PrivateMessage( NULL );
+ return qtrue;
+ }
+ else if( !Q_stricmp( cmd, "a" ) || !Q_stricmp( cmd, "say_admins" ))
+ {
+ G_Say( NULL, NULL, SAY_ADMINS, ConcatArgs( 1 ) );
+ return qtrue;
+ }
+ else if( !Q_stricmp( cmd, "ha" ) || !Q_stricmp( cmd, "say_hadmins" ))
+ {
+ G_Say( NULL, NULL, SAY_HADMINS, ConcatArgs( 1 ) );
+ return qtrue;
+ }
+
+ G_Printf( "unknown command: %s\n", cmd );
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
diff --git a/src/game/g_syscalls.asm b/src/game/g_syscalls.asm
new file mode 100644
index 0000000..242c2ad
--- /dev/null
+++ b/src/game/g_syscalls.asm
@@ -0,0 +1,68 @@
+code
+
+equ trap_Printf -1
+equ trap_Error -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Register -4
+equ trap_Cvar_Update -5
+equ trap_Cvar_Set -6
+equ trap_Cvar_VariableIntegerValue -7
+equ trap_Cvar_VariableStringBuffer -8
+equ trap_Argc -9
+equ trap_Argv -10
+equ trap_FS_FOpenFile -11
+equ trap_FS_Read -12
+equ trap_FS_Write -13
+equ trap_FS_FCloseFile -14
+equ trap_SendConsoleCommand -15
+equ trap_LocateGameData -16
+equ trap_DropClient -17
+equ trap_SendServerCommand -18
+equ trap_SetConfigstring -19
+equ trap_GetConfigstring -20
+equ trap_SetConfigstringRestrictions -21
+equ trap_GetUserinfo -22
+equ trap_SetUserinfo -23
+equ trap_GetServerinfo -24
+equ trap_SetBrushModel -25
+equ trap_Trace -26
+equ trap_PointContents -27
+equ trap_InPVS -28
+equ trap_InPVSIgnorePortals -29
+equ trap_AdjustAreaPortalState -30
+equ trap_AreasConnected -31
+equ trap_LinkEntity -32
+equ trap_UnlinkEntity -33
+equ trap_EntitiesInBox -34
+equ trap_EntityContact -35
+equ trap_GetUsercmd -36
+equ trap_GetEntityToken -37
+equ trap_FS_GetFileList -38
+equ trap_RealTime -39
+equ trap_SnapVector -40
+equ trap_TraceCapsule -41
+equ trap_EntityContactCapsule -42
+equ trap_FS_Seek -43
+
+equ trap_Parse_AddGlobalDefine -44
+equ trap_Parse_LoadSource -45
+equ trap_Parse_FreeSource -46
+equ trap_Parse_ReadToken -47
+equ trap_Parse_SourceFileAndLine -48
+
+equ trap_SendGameStat -49
+
+equ trap_AddCommand -50
+equ trap_RemoveCommand -51
+
+equ memset -101
+equ memcpy -102
+equ strncpy -103
+equ sin -104
+equ cos -105
+equ atan2 -106
+equ sqrt -107
+equ floor -111
+equ ceil -112
+equ testPrintInt -113
+equ testPrintFloat -114
diff --git a/src/game/g_syscalls.c b/src/game/g_syscalls.c
new file mode 100644
index 0000000..6e3dc26
--- /dev/null
+++ b/src/game/g_syscalls.c
@@ -0,0 +1,284 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+// this file is only included when building a dll
+// g_syscalls.asm is included instead when building a qvm
+
+static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1;
+
+
+Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) )
+{
+ syscall = syscallptr;
+}
+
+int PASSFLOAT( float x )
+{
+ float floatTemp;
+ floatTemp = x;
+ return *(int *)&floatTemp;
+}
+
+void trap_Printf( const char *fmt )
+{
+ syscall( G_PRINT, fmt );
+}
+
+void trap_Error( const char *fmt )
+{
+ syscall( G_ERROR, fmt );
+}
+
+int trap_Milliseconds( void )
+{
+ return syscall( G_MILLISECONDS );
+}
+int trap_Argc( void )
+{
+ return syscall( G_ARGC );
+}
+
+void trap_Argv( int n, char *buffer, int bufferLength )
+{
+ syscall( G_ARGV, n, buffer, bufferLength );
+}
+
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode )
+{
+ return syscall( G_FS_FOPEN_FILE, qpath, f, mode );
+}
+
+void trap_FS_Read( void *buffer, int len, fileHandle_t f )
+{
+ syscall( G_FS_READ, buffer, len, f );
+}
+
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f )
+{
+ syscall( G_FS_WRITE, buffer, len, f );
+}
+
+void trap_FS_FCloseFile( fileHandle_t f )
+{
+ syscall( G_FS_FCLOSE_FILE, f );
+}
+
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize )
+{
+ return syscall( G_FS_GETFILELIST, path, extension, listbuf, bufsize );
+}
+
+void trap_SendConsoleCommand( int exec_when, const char *text )
+{
+ syscall( G_SEND_CONSOLE_COMMAND, exec_when, text );
+}
+
+void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags )
+{
+ syscall( G_CVAR_REGISTER, cvar, var_name, value, flags );
+}
+
+void trap_Cvar_Update( vmCvar_t *cvar )
+{
+ syscall( G_CVAR_UPDATE, cvar );
+}
+
+void trap_Cvar_Set( const char *var_name, const char *value )
+{
+ syscall( G_CVAR_SET, var_name, value );
+}
+
+int trap_Cvar_VariableIntegerValue( const char *var_name )
+{
+ return syscall( G_CVAR_VARIABLE_INTEGER_VALUE, var_name );
+}
+
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize )
+{
+ syscall( G_CVAR_VARIABLE_STRING_BUFFER, var_name, buffer, bufsize );
+}
+
+
+void trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t,
+ playerState_t *clients, int sizeofGClient )
+{
+ syscall( G_LOCATE_GAME_DATA, gEnts, numGEntities, sizeofGEntity_t, clients, sizeofGClient );
+}
+
+void trap_DropClient( int clientNum, const char *reason )
+{
+ syscall( G_DROP_CLIENT, clientNum, reason );
+}
+
+void trap_SendServerCommand( int clientNum, const char *text )
+{
+ syscall( G_SEND_SERVER_COMMAND, clientNum, text );
+}
+
+void trap_SetConfigstring( int num, const char *string )
+{
+ syscall( G_SET_CONFIGSTRING, num, string );
+}
+
+void trap_GetConfigstring( int num, char *buffer, int bufferSize )
+{
+ syscall( G_GET_CONFIGSTRING, num, buffer, bufferSize );
+}
+
+void trap_GetUserinfo( int num, char *buffer, int bufferSize )
+{
+ syscall( G_GET_USERINFO, num, buffer, bufferSize );
+}
+
+void trap_SetUserinfo( int num, const char *buffer )
+{
+ syscall( G_SET_USERINFO, num, buffer );
+}
+
+void trap_GetServerinfo( char *buffer, int bufferSize )
+{
+ syscall( G_GET_SERVERINFO, buffer, bufferSize );
+}
+
+void trap_SetBrushModel( gentity_t *ent, const char *name )
+{
+ syscall( G_SET_BRUSH_MODEL, ent, name );
+}
+
+void trap_Trace( trace_t *results, const vec3_t start, const vec3_t mins,
+ const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask )
+{
+ syscall( G_TRACE, results, start, mins, maxs, end, passEntityNum, contentmask );
+}
+
+void trap_TraceCapsule( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask )
+{
+ syscall( G_TRACECAPSULE, results, start, mins, maxs, end, passEntityNum, contentmask );
+}
+
+int trap_PointContents( const vec3_t point, int passEntityNum )
+{
+ return syscall( G_POINT_CONTENTS, point, passEntityNum );
+}
+
+
+qboolean trap_InPVS( const vec3_t p1, const vec3_t p2 )
+{
+ return syscall( G_IN_PVS, p1, p2 );
+}
+
+qboolean trap_InPVSIgnorePortals( const vec3_t p1, const vec3_t p2 )
+{
+ return syscall( G_IN_PVS_IGNORE_PORTALS, p1, p2 );
+}
+
+void trap_AdjustAreaPortalState( gentity_t *ent, qboolean open )
+{
+ syscall( G_ADJUST_AREA_PORTAL_STATE, ent, open );
+}
+
+qboolean trap_AreasConnected( int area1, int area2 )
+{
+ return syscall( G_AREAS_CONNECTED, area1, area2 );
+}
+
+void trap_LinkEntity( gentity_t *ent )
+{
+ syscall( G_LINKENTITY, ent );
+}
+
+void trap_UnlinkEntity( gentity_t *ent )
+{
+ syscall( G_UNLINKENTITY, ent );
+}
+
+
+int trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *list, int maxcount )
+{
+ return syscall( G_ENTITIES_IN_BOX, mins, maxs, list, maxcount );
+}
+
+qboolean trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent )
+{
+ return syscall( G_ENTITY_CONTACT, mins, maxs, ent );
+}
+
+qboolean trap_EntityContactCapsule( const vec3_t mins, const vec3_t maxs, const gentity_t *ent )
+{
+ return syscall( G_ENTITY_CONTACTCAPSULE, mins, maxs, ent );
+}
+
+void trap_GetUsercmd( int clientNum, usercmd_t *cmd )
+{
+ syscall( G_GET_USERCMD, clientNum, cmd );
+}
+
+qboolean trap_GetEntityToken( char *buffer, int bufferSize )
+{
+ return syscall( G_GET_ENTITY_TOKEN, buffer, bufferSize );
+}
+
+int trap_RealTime( qtime_t *qtime )
+{
+ return syscall( G_REAL_TIME, qtime );
+}
+
+void trap_SnapVector( float *v )
+{
+ syscall( G_SNAPVECTOR, v );
+ return;
+}
+
+void trap_SendGameStat( const char *data )
+{
+ syscall( G_SEND_GAMESTAT, data );
+ return;
+}
+
+int trap_Parse_AddGlobalDefine( char *define )
+{
+ return syscall( G_PARSE_ADD_GLOBAL_DEFINE, define );
+}
+
+int trap_Parse_LoadSource( const char *filename )
+{
+ return syscall( G_PARSE_LOAD_SOURCE, filename );
+}
+
+int trap_Parse_FreeSource( int handle )
+{
+ return syscall( G_PARSE_FREE_SOURCE, handle );
+}
+
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token )
+{
+ return syscall( G_PARSE_READ_TOKEN, handle, pc_token );
+}
+
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line )
+{
+ return syscall( G_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line );
+}
+
diff --git a/src/game/g_target.c b/src/game/g_target.c
new file mode 100644
index 0000000..467920a
--- /dev/null
+++ b/src/game/g_target.c
@@ -0,0 +1,478 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+//==========================================================
+
+/*QUAKED target_delay (1 0 0) (-8 -8 -8) (8 8 8)
+"wait" seconds to pause before firing targets.
+"random" delay variance, total delay = delay +/- random seconds
+*/
+void Think_Target_Delay( gentity_t *ent )
+{
+ G_UseTargets( ent, ent->activator );
+}
+
+void Use_Target_Delay( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000;
+ ent->think = Think_Target_Delay;
+ ent->activator = activator;
+}
+
+void SP_target_delay( gentity_t *ent )
+{
+ // check delay for backwards compatability
+ if( !G_SpawnFloat( "delay", "0", &ent->wait ) )
+ G_SpawnFloat( "wait", "1", &ent->wait );
+
+ if( !ent->wait )
+ ent->wait = 1;
+
+ ent->use = Use_Target_Delay;
+}
+
+
+//==========================================================
+
+/*QUAKED target_score (1 0 0) (-8 -8 -8) (8 8 8)
+"count" number of points to add, default 1
+
+The activator is given this many points.
+*/
+void Use_Target_Score( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ if( !activator )
+ return;
+
+ AddScore( activator, ent->count );
+}
+
+void SP_target_score( gentity_t *ent )
+{
+ if( !ent->count )
+ ent->count = 1;
+
+ ent->use = Use_Target_Score;
+}
+
+
+//==========================================================
+
+/*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8) humanteam alienteam private
+"message" text to print
+If "private", only the activator gets the message. If no checks, all clients get the message.
+*/
+void Use_Target_Print( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ if( activator && activator->client && ( ent->spawnflags & 4 ) )
+ {
+ trap_SendServerCommand( activator-g_entities, va( "cp \"%s\"", ent->message ) );
+ return;
+ }
+
+ if( ent->spawnflags & 3 )
+ {
+ if( ent->spawnflags & 1 )
+ G_TeamCommand( PTE_HUMANS, va( "cp \"%s\"", ent->message ) );
+ if( ent->spawnflags & 2 )
+ G_TeamCommand( PTE_ALIENS, va( "cp \"%s\"", ent->message ) );
+
+ return;
+ }
+
+ trap_SendServerCommand( -1, va("cp \"%s\"", ent->message ) );
+}
+
+void SP_target_print( gentity_t *ent )
+{
+ ent->use = Use_Target_Print;
+}
+
+
+//==========================================================
+
+
+/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off global activator
+"noise" wav file to play
+
+A global sound will play full volume throughout the level.
+Activator sounds will play on the player that activated the target.
+Global and activator sounds can't be combined with looping.
+Normal sounds play each time the target is used.
+Looped sounds will be toggled by use functions.
+Multiple identical looping sounds will just increase volume without any speed cost.
+"wait" : Seconds between auto triggerings, 0 = don't auto trigger
+"random" wait variance, default is 0
+*/
+void Use_Target_Speaker( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ if( ent->spawnflags & 3 )
+ { // looping sound toggles
+ if( ent->s.loopSound )
+ ent->s.loopSound = 0; // turn it off
+ else
+ ent->s.loopSound = ent->noise_index; // start it
+ }
+ else
+ {
+ // normal sound
+ if( ent->spawnflags & 8 && activator )
+ G_AddEvent( activator, EV_GENERAL_SOUND, ent->noise_index );
+ else if( ent->spawnflags & 4 )
+ G_AddEvent( ent, EV_GLOBAL_SOUND, ent->noise_index );
+ else
+ G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index );
+ }
+}
+
+void SP_target_speaker( gentity_t *ent )
+{
+ char buffer[ MAX_QPATH ];
+ char *s;
+
+ G_SpawnFloat( "wait", "0", &ent->wait );
+ G_SpawnFloat( "random", "0", &ent->random );
+
+ if( !G_SpawnString( "noise", "NOSOUND", &s ) )
+ G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) );
+
+ // force all client reletive sounds to be "activator" speakers that
+ // play on the entity that activates it
+ if( s[ 0 ] == '*' )
+ ent->spawnflags |= 8;
+
+ if( !strstr( s, ".wav" ) )
+ Com_sprintf( buffer, sizeof( buffer ), "%s.wav", s );
+ else
+ Q_strncpyz( buffer, s, sizeof( buffer ) );
+
+ ent->noise_index = G_SoundIndex( buffer );
+
+ // a repeating speaker can be done completely client side
+ ent->s.eType = ET_SPEAKER;
+ ent->s.eventParm = ent->noise_index;
+ ent->s.frame = ent->wait * 10;
+ ent->s.clientNum = ent->random * 10;
+
+
+ // check for prestarted looping sound
+ if( ent->spawnflags & 1 )
+ ent->s.loopSound = ent->noise_index;
+
+ ent->use = Use_Target_Speaker;
+
+ if( ent->spawnflags & 4 )
+ ent->r.svFlags |= SVF_BROADCAST;
+
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+
+ // must link the entity so we get areas and clusters so
+ // the server can determine who to send updates to
+ trap_LinkEntity( ent );
+}
+
+//==========================================================
+
+void target_teleporter_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ gentity_t *dest;
+
+ if( !activator || !activator->client )
+ return;
+
+ dest = G_PickTarget( self->target );
+
+ if( !dest )
+ {
+ G_Printf( "Couldn't find teleporter destination\n" );
+ return;
+ }
+
+ TeleportPlayer( activator, dest->s.origin, dest->s.angles );
+}
+
+/*QUAKED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8)
+The activator will be teleported away.
+*/
+void SP_target_teleporter( gentity_t *self )
+{
+ if( !self->targetname )
+ G_Printf( "untargeted %s at %s\n", self->classname, vtos( self->s.origin ) );
+
+ self->use = target_teleporter_use;
+}
+
+//==========================================================
+
+
+/*QUAKED target_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM
+This doesn't perform any actions except fire its targets.
+The activator can be forced to be from a certain team.
+if RANDOM is checked, only one of the targets will be fired, not all of them
+*/
+void target_relay_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( ( self->spawnflags & 1 ) && activator && activator->client &&
+ activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ if( ( self->spawnflags & 2 ) && activator && activator->client &&
+ activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return;
+
+ if( self->spawnflags & 4 )
+ {
+ gentity_t *ent;
+
+ ent = G_PickTarget( self->target );
+ if( ent && ent->use )
+ ent->use( ent, self, activator );
+
+ return;
+ }
+
+ G_UseTargets( self, activator );
+}
+
+void SP_target_relay( gentity_t *self )
+{
+ self->use = target_relay_use;
+}
+
+
+//==========================================================
+
+/*QUAKED target_kill (.5 .5 .5) (-8 -8 -8) (8 8 8)
+Kills the activator.
+*/
+void target_kill_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( !activator )
+ return;
+
+ G_Damage( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
+}
+
+void SP_target_kill( gentity_t *self )
+{
+ self->use = target_kill_use;
+}
+
+/*QUAKED target_position (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for in-game calculation, like jumppad targets.
+*/
+void SP_target_position( gentity_t *self )
+{
+ G_SetOrigin( self, self->s.origin );
+}
+
+static void target_location_linkup( gentity_t *ent )
+{
+ int i;
+ int n;
+
+ if( level.locationLinked )
+ return;
+
+ level.locationLinked = qtrue;
+
+ level.locationHead = NULL;
+
+ trap_SetConfigstring( CS_LOCATIONS, "unknown" );
+
+ for( i = 0, ent = g_entities, n = 1; i < level.num_entities; i++, ent++)
+ {
+ if( ent->classname && !Q_stricmp( ent->classname, "target_location" ) )
+ {
+ // lets overload some variables!
+ ent->health = n; // use for location marking
+ trap_SetConfigstring( CS_LOCATIONS + n, ent->message );
+ n++;
+ ent->nextTrain = level.locationHead;
+ level.locationHead = ent;
+ }
+ }
+ // All linked together now
+}
+
+/*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8)
+Set "message" to the name of this location.
+Set "count" to 0-7 for color.
+0:white 1:red 2:green 3:yellow 4:blue 5:cyan 6:magenta 7:white
+
+Closest target_location in sight used for the location, if none
+in site, closest in distance
+*/
+void SP_target_location( gentity_t *self )
+{
+ self->think = target_location_linkup;
+ self->nextthink = level.time + 200; // Let them all spawn first
+
+ G_SetOrigin( self, self->s.origin );
+}
+
+
+/*
+===============
+target_rumble_think
+===============
+*/
+void target_rumble_think( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+
+ if( self->last_move_time < level.time )
+ self->last_move_time = level.time + 0.5;
+
+ for( i = 0, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( !ent->client )
+ continue;
+
+ if( ent->client->ps.groundEntityNum == ENTITYNUM_NONE )
+ continue;
+
+ ent->client->ps.groundEntityNum = ENTITYNUM_NONE;
+ ent->client->ps.velocity[ 0 ] += crandom( ) * 150;
+ ent->client->ps.velocity[ 1 ] += crandom( ) * 150;
+ ent->client->ps.velocity[ 2 ] = self->speed;
+ }
+
+ if( level.time < self->timestamp )
+ self->nextthink = level.time + FRAMETIME;
+}
+
+/*
+===============
+target_rumble_use
+===============
+*/
+void target_rumble_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ self->timestamp = level.time + ( self->count * FRAMETIME );
+ self->nextthink = level.time + FRAMETIME;
+ self->activator = activator;
+ self->last_move_time = 0;
+}
+
+/*
+===============
+SP_target_rumble
+===============
+*/
+void SP_target_rumble( gentity_t *self )
+{
+ if( !self->targetname )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname,
+ vtos( self->s.origin ) );
+ }
+
+ if( !self->count )
+ self->count = 10;
+
+ if( !self->speed )
+ self->speed = 100;
+
+ self->think = target_rumble_think;
+ self->use = target_rumble_use;
+}
+
+/*
+===============
+target_alien_win_use
+===============
+*/
+void target_alien_win_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ level.uncondAlienWin = qtrue;
+}
+
+/*
+===============
+SP_target_alien_win
+===============
+*/
+void SP_target_alien_win( gentity_t *self )
+{
+ self->use = target_alien_win_use;
+}
+
+/*
+===============
+target_human_win_use
+===============
+*/
+void target_human_win_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ level.uncondHumanWin = qtrue;
+}
+
+/*
+===============
+SP_target_human_win
+===============
+*/
+void SP_target_human_win( gentity_t *self )
+{
+ self->use = target_human_win_use;
+}
+
+/*
+===============
+target_hurt_use
+===============
+*/
+void target_hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ // hurt the activator
+ if( !activator || !activator->takedamage )
+ return;
+
+ G_Damage( activator, self, self, NULL, NULL, self->damage, 0, MOD_TRIGGER_HURT );
+}
+
+/*
+===============
+SP_target_hurt
+===============
+*/
+void SP_target_hurt( gentity_t *self )
+{
+ if( !self->targetname )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname,
+ vtos( self->s.origin ) );
+ }
+
+ if( !self->damage )
+ self->damage = 5;
+
+ self->use = target_hurt_use;
+}
diff --git a/src/game/g_team.c b/src/game/g_team.c
new file mode 100644
index 0000000..1d8d102
--- /dev/null
+++ b/src/game/g_team.c
@@ -0,0 +1,276 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+// NULL for everyone
+void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... )
+{
+ char msg[ 1024 ];
+ va_list argptr;
+ char *p;
+
+ va_start( argptr,fmt );
+
+ if( vsprintf( msg, fmt, argptr ) > sizeof( msg ) )
+ G_Error ( "PrintMsg overrun" );
+
+ va_end( argptr );
+
+ // double quotes are bad
+ while( ( p = strchr( msg, '"' ) ) != NULL )
+ *p = '\'';
+
+ trap_SendServerCommand( ( ( ent == NULL ) ? -1 : ent-g_entities ), va( "print \"%s\"", msg ) );
+}
+
+
+/*
+==============
+OnSameTeam
+==============
+*/
+qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 )
+{
+ if( !ent1->client || !ent2->client )
+ return qfalse;
+
+ if( ent1->client->pers.teamSelection == ent2->client->pers.teamSelection )
+ return qtrue;
+
+ return qfalse;
+}
+
+/*
+===========
+Team_GetLocation
+
+Report a location for the player. Uses placed nearby target_location entities
+============
+*/
+gentity_t *Team_GetLocation( gentity_t *ent )
+{
+ gentity_t *eloc, *best;
+ float bestlen, len;
+ vec3_t origin;
+
+ best = NULL;
+ bestlen = 3.0f * 8192.0f * 8192.0f;
+
+ VectorCopy( ent->r.currentOrigin, origin );
+
+ for( eloc = level.locationHead; eloc; eloc = eloc->nextTrain )
+ {
+ len = ( origin[ 0 ] - eloc->r.currentOrigin[ 0 ] ) * ( origin[ 0 ] - eloc->r.currentOrigin[ 0 ] )
+ + ( origin[ 1 ] - eloc->r.currentOrigin[ 1 ] ) * ( origin[ 1 ] - eloc->r.currentOrigin[ 1 ] )
+ + ( origin[ 2 ] - eloc->r.currentOrigin[ 2 ] ) * ( origin[ 2 ] - eloc->r.currentOrigin[ 2 ] );
+
+ if( len > bestlen )
+ continue;
+
+ if( !trap_InPVS( origin, eloc->r.currentOrigin ) )
+ continue;
+
+ bestlen = len;
+ best = eloc;
+ }
+
+ return best;
+}
+
+
+/*
+===========
+Team_GetLocationMsg
+
+Report a location message for the player. Uses placed nearby target_location entities
+============
+*/
+qboolean Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen )
+{
+ gentity_t *best;
+
+ best = Team_GetLocation( ent );
+
+ if( !best )
+ return qfalse;
+
+ if( best->count )
+ {
+ if( best->count < 0 )
+ best->count = 0;
+
+ if( best->count > 7 )
+ best->count = 7;
+
+ Com_sprintf( loc, loclen, "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, best->count + '0', best->message );
+ }
+ else
+ Com_sprintf( loc, loclen, "%s", best->message );
+
+ return qtrue;
+}
+
+
+/*---------------------------------------------------------------------------*/
+
+static int QDECL SortClients( const void *a, const void *b )
+{
+ return *(int *)a - *(int *)b;
+}
+
+
+/*
+==================
+TeamplayLocationsMessage
+
+Format:
+ clientNum location health armor weapon powerups
+
+==================
+*/
+void TeamplayInfoMessage( gentity_t *ent )
+{
+ char entry[ 1024 ];
+ char string[ 8192 ];
+ int stringlength;
+ int i, j;
+ gentity_t *player;
+ int cnt;
+ int h, a = 0;
+ int clients[ TEAM_MAXOVERLAY ];
+
+ if( ! ent->client->pers.teamInfo )
+ return;
+
+ // figure out what client should be on the display
+ // we are limited to 8, but we want to use the top eight players
+ // but in client order (so they don't keep changing position on the overlay)
+ for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++ )
+ {
+ player = g_entities + level.sortedClients[ i ];
+
+ if( player->inuse && player->client->sess.sessionTeam ==
+ ent->client->sess.sessionTeam )
+ clients[ cnt++ ] = level.sortedClients[ i ];
+ }
+
+ // We have the top eight players, sort them by clientNum
+ qsort( clients, cnt, sizeof( clients[ 0 ] ), SortClients );
+
+ // send the latest information on all clients
+ string[ 0 ] = 0;
+ stringlength = 0;
+
+ for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++)
+ {
+ player = g_entities + i;
+
+ if( player->inuse && player->client->sess.sessionTeam ==
+ ent->client->sess.sessionTeam )
+ {
+ h = player->client->ps.stats[ STAT_HEALTH ];
+
+ if( h < 0 )
+ h = 0;
+
+ Com_sprintf( entry, sizeof( entry ),
+ " %i %i %i %i %i %i",
+// level.sortedClients[i], player->client->pers.teamState.location, h, a,
+ i, player->client->pers.teamState.location, h, a,
+ player->client->ps.weapon, player->s.misc );
+
+ j = strlen( entry );
+
+ if( stringlength + j > sizeof( string ) )
+ break;
+
+ strcpy( string + stringlength, entry );
+ stringlength += j;
+ cnt++;
+ }
+ }
+
+ trap_SendServerCommand( ent - g_entities, va( "tinfo %i %s", cnt, string ) );
+}
+
+void CheckTeamStatus( void )
+{
+ int i;
+ gentity_t *loc, *ent;
+
+ if( level.time - level.lastTeamLocationTime > TEAM_LOCATION_UPDATE_TIME )
+ {
+ level.lastTeamLocationTime = level.time;
+
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ ent = g_entities + i;
+ if( ent->client->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( ent->inuse && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ||
+ ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) )
+ {
+
+ loc = Team_GetLocation( ent );
+
+ if( loc )
+ ent->client->pers.teamState.location = loc->health;
+ else
+ ent->client->pers.teamState.location = 0;
+ }
+ }
+
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ ent = g_entities + i;
+ if( ent->client->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( ent->inuse && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ||
+ ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) )
+ TeamplayInfoMessage( ent );
+ }
+ }
+
+ //Warn on unbalanced teams
+ if ( g_teamImbalanceWarnings.integer && !level.intermissiontime && level.time - level.lastTeamUnbalancedTime > ( g_teamImbalanceWarnings.integer * 1000 ) && level.numTeamWarnings<3 )
+ {
+ level.lastTeamUnbalancedTime = level.time;
+ if (level.numAlienSpawns > 0 && level.numHumanClients - level.numAlienClients > 2)
+ {
+ trap_SendServerCommand (-1, "print \"Teams are unbalanced. Humans have more players.\n Humans will keep their points when switching teams.\n\"");
+ level.numTeamWarnings++;
+ }
+ else if (level.numHumanSpawns > 0 && level.numAlienClients - level.numHumanClients > 2)
+ {
+ trap_SendServerCommand (-1, "print \"Teams are unbalanced. Aliens have more players.\n Aliens will keep their points when switching teams.\n\"");
+ level.numTeamWarnings++;
+ }
+ else
+ {
+ level.numTeamWarnings = 0;
+ }
+ }
+}
diff --git a/src/game/g_trigger.c b/src/game/g_trigger.c
new file mode 100644
index 0000000..0ac34bb
--- /dev/null
+++ b/src/game/g_trigger.c
@@ -0,0 +1,1141 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+
+void InitTrigger( gentity_t *self )
+{
+ if( !VectorCompare( self->s.angles, vec3_origin ) )
+ G_SetMovedir( self->s.angles, self->movedir );
+
+ trap_SetBrushModel( self, self->model );
+ self->r.contents = CONTENTS_TRIGGER; // replaces the -1 from trap_SetBrushModel
+ self->r.svFlags = SVF_NOCLIENT;
+}
+
+
+// the wait time has passed, so set back up for another activation
+void multi_wait( gentity_t *ent )
+{
+ ent->nextthink = 0;
+}
+
+
+// the trigger was just activated
+// ent->activator should be set to the activator so it can be held through a delay
+// so wait for the delay time before firing
+void multi_trigger( gentity_t *ent, gentity_t *activator )
+{
+ ent->activator = activator;
+ if( ent->nextthink )
+ return; // can't retrigger until the wait is over
+
+ if( activator->client )
+ {
+ if( ( ent->spawnflags & 1 ) &&
+ activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ if( ( ent->spawnflags & 2 ) &&
+ activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return;
+ }
+
+ G_UseTargets( ent, ent->activator );
+
+ if( ent->wait > 0 )
+ {
+ ent->think = multi_wait;
+ ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000;
+ }
+ else
+ {
+ // we can't just remove (self) here, because this is a touch function
+ // called while looping through area links...
+ ent->touch = 0;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = G_FreeEntity;
+ }
+}
+
+void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ multi_trigger( ent, activator );
+}
+
+void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ if( !other->client && other->s.eType != ET_BUILDABLE )
+ return;
+
+ multi_trigger( self, other );
+}
+
+/*QUAKED trigger_multiple (.5 .5 .5) ?
+"wait" : Seconds between triggerings, 0.5 default, -1 = one time only.
+"random" wait variance, default is 0
+Variable sized repeatable trigger. Must be targeted at one or more entities.
+so, the basic time between firing is a random time between
+(wait - random) and (wait + random)
+*/
+void SP_trigger_multiple( gentity_t *ent )
+{
+ G_SpawnFloat( "wait", "0.5", &ent->wait );
+ G_SpawnFloat( "random", "0", &ent->random );
+
+ if( ent->random >= ent->wait && ent->wait >= 0 )
+ {
+ ent->random = ent->wait - FRAMETIME;
+ G_Printf( "trigger_multiple has random >= wait\n" );
+ }
+
+ ent->touch = Touch_Multi;
+ ent->use = Use_Multi;
+
+ InitTrigger( ent );
+ trap_LinkEntity( ent );
+}
+
+
+
+/*
+==============================================================================
+
+trigger_always
+
+==============================================================================
+*/
+
+void trigger_always_think( gentity_t *ent )
+{
+ G_UseTargets( ent, ent );
+ G_FreeEntity( ent );
+}
+
+/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
+This trigger will always fire. It is activated by the world.
+*/
+void SP_trigger_always( gentity_t *ent )
+{
+ // we must have some delay to make sure our use targets are present
+ ent->nextthink = level.time + 300;
+ ent->think = trigger_always_think;
+}
+
+
+/*
+==============================================================================
+
+trigger_push
+
+==============================================================================
+*/
+
+void trigger_push_touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ if( !other->client )
+ return;
+}
+
+
+/*
+=================
+AimAtTarget
+
+Calculate origin2 so the target apogee will be hit
+=================
+*/
+void AimAtTarget( gentity_t *self )
+{
+ gentity_t *ent;
+ vec3_t origin;
+ float height, gravity, time, forward;
+ float dist;
+
+ VectorAdd( self->r.absmin, self->r.absmax, origin );
+ VectorScale( origin, 0.5, origin );
+
+ ent = G_PickTarget( self->target );
+
+ if( !ent )
+ {
+ G_FreeEntity( self );
+ return;
+ }
+
+ height = ent->s.origin[ 2 ] - origin[ 2 ];
+ gravity = g_gravity.value;
+ time = sqrt( height / ( 0.5 * gravity ) );
+
+ if( !time )
+ {
+ G_FreeEntity( self );
+ return;
+ }
+
+ // set s.origin2 to the push velocity
+ VectorSubtract( ent->s.origin, origin, self->s.origin2 );
+ self->s.origin2[ 2 ] = 0;
+ dist = VectorNormalize( self->s.origin2 );
+
+ forward = dist / time;
+ VectorScale( self->s.origin2, forward, self->s.origin2 );
+
+ self->s.origin2[ 2 ] = time * gravity;
+}
+
+
+/*QUAKED trigger_push (.5 .5 .5) ?
+Must point at a target_position, which will be the apex of the leap.
+This will be client side predicted, unlike target_push
+*/
+void SP_trigger_push( gentity_t *self )
+{
+ InitTrigger( self );
+
+ // unlike other triggers, we need to send this one to the client
+ self->r.svFlags &= ~SVF_NOCLIENT;
+
+ self->s.eType = ET_PUSH_TRIGGER;
+ self->touch = trigger_push_touch;
+ self->think = AimAtTarget;
+ self->nextthink = level.time + FRAMETIME;
+ trap_LinkEntity( self );
+}
+
+
+void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( !activator->client )
+ return;
+
+ if( activator->client->ps.pm_type != PM_NORMAL )
+ return;
+
+ VectorCopy( self->s.origin2, activator->client->ps.velocity );
+
+}
+
+/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8)
+Pushes the activator in the direction.of angle, or towards a target apex.
+"speed" defaults to 1000
+*/
+void SP_target_push( gentity_t *self )
+{
+ if( !self->speed )
+ self->speed = 1000;
+
+ G_SetMovedir( self->s.angles, self->s.origin2 );
+ VectorScale( self->s.origin2, self->speed, self->s.origin2 );
+
+ if( self->target )
+ {
+ VectorCopy( self->s.origin, self->r.absmin );
+ VectorCopy( self->s.origin, self->r.absmax );
+ self->think = AimAtTarget;
+ self->nextthink = level.time + FRAMETIME;
+ }
+
+ self->use = Use_target_push;
+}
+
+/*
+==============================================================================
+
+trigger_teleport
+
+==============================================================================
+*/
+
+void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ gentity_t *dest;
+
+ if( self->s.eFlags & EF_NODRAW )
+ return;
+
+ if( !other->client )
+ return;
+
+ if( other->client->ps.pm_type == PM_DEAD )
+ return;
+
+ // Spectators only?
+ if( ( self->spawnflags & 1 ) &&
+ other->client->sess.sessionTeam != TEAM_SPECTATOR )
+ return;
+
+
+ dest = G_PickTarget( self->target );
+
+ if( !dest )
+ {
+ G_Printf( "Couldn't find teleporter destination\n" );
+ return;
+ }
+
+ TeleportPlayer( other, dest->s.origin, dest->s.angles );
+}
+
+/*
+===============
+trigger_teleport_use
+===============
+*/
+void trigger_teleporter_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ ent->s.eFlags ^= EF_NODRAW;
+}
+
+
+/*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR SPAWN_DISABLED
+Allows client side prediction of teleportation events.
+Must point at a target_position, which will be the teleport destination.
+
+If spectator is set, only spectators can use this teleport
+Spectator teleporters are not normally placed in the editor, but are created
+automatically near doors to allow spectators to move through them
+*/
+void SP_trigger_teleport( gentity_t *self )
+{
+ InitTrigger( self );
+
+ // unlike other triggers, we need to send this one to the client
+ // unless is a spectator trigger
+ if( self->spawnflags & 1 )
+ self->r.svFlags |= SVF_NOCLIENT;
+ else
+ self->r.svFlags &= ~SVF_NOCLIENT;
+
+ // SPAWN_DISABLED
+ if( self->spawnflags & 2 )
+ self->s.eFlags |= EF_NODRAW;
+
+ self->s.eType = ET_TELEPORT_TRIGGER;
+ self->touch = trigger_teleporter_touch;
+ self->use = trigger_teleporter_use;
+
+ trap_LinkEntity( self );
+}
+
+
+/*
+==============================================================================
+
+trigger_hurt
+
+==============================================================================
+*/
+
+/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW
+Any entity that touches this will be hurt.
+It does dmg points of damage each server frame
+Targeting the trigger will toggle its on / off state.
+
+SILENT supresses playing the sound
+SLOW changes the damage rate to once per second
+NO_PROTECTION *nothing* stops the damage
+
+"dmg" default 5 (whole numbers only)
+
+*/
+void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( self->r.linked )
+ trap_UnlinkEntity( self );
+ else
+ trap_LinkEntity( self );
+}
+
+void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ int dflags;
+
+ if( !other->takedamage )
+ return;
+
+ if( self->timestamp > level.time )
+ return;
+
+ if( self->spawnflags & 16 )
+ self->timestamp = level.time + 1000;
+ else
+ self->timestamp = level.time + FRAMETIME;
+
+ // play sound
+ if( !( self->spawnflags & 4 ) )
+ G_Sound( other, CHAN_AUTO, self->noise_index );
+
+ if( self->spawnflags & 8 )
+ dflags = DAMAGE_NO_PROTECTION;
+ else
+ dflags = 0;
+
+ G_Damage( other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT );
+}
+
+void SP_trigger_hurt( gentity_t *self )
+{
+ InitTrigger( self );
+
+ self->noise_index = G_SoundIndex( "sound/misc/electro.wav" );
+ self->touch = hurt_touch;
+
+ if( self->damage <= 0 )
+ self->damage = 5;
+
+ self->r.contents = CONTENTS_TRIGGER;
+
+ if( self->spawnflags & 2 )
+ self->use = hurt_use;
+
+ // link in to the world if starting active
+ if( !( self->spawnflags & 1 ) )
+ trap_LinkEntity( self );
+}
+
+
+/*
+==============================================================================
+
+timer
+
+==============================================================================
+*/
+
+
+/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
+This should be renamed trigger_timer...
+Repeatedly fires its targets.
+Can be turned on or off by using.
+
+"wait" base time between triggering all targets, default is 1
+"random" wait variance, default is 0
+so, the basic time between firing is a random time between
+(wait - random) and (wait + random)
+
+*/
+void func_timer_think( gentity_t *self )
+{
+ G_UseTargets( self, self->activator );
+ // set time before next firing
+ self->nextthink = level.time + 1000 * ( self->wait + crandom( ) * self->random );
+}
+
+void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ self->activator = activator;
+
+ // if on, turn it off
+ if( self->nextthink )
+ {
+ self->nextthink = 0;
+ return;
+ }
+
+ // turn it on
+ func_timer_think( self );
+}
+
+void SP_func_timer( gentity_t *self )
+{
+ G_SpawnFloat( "random", "1", &self->random );
+ G_SpawnFloat( "wait", "1", &self->wait );
+
+ self->use = func_timer_use;
+ self->think = func_timer_think;
+
+ if( self->random >= self->wait )
+ {
+ self->random = self->wait - FRAMETIME;
+ G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
+ }
+
+ if( self->spawnflags & 1 )
+ {
+ self->nextthink = level.time + FRAMETIME;
+ self->activator = self;
+ }
+
+ self->r.svFlags = SVF_NOCLIENT;
+}
+
+
+/*
+===============
+G_Checktrigger_stages
+
+Called when stages change
+===============
+*/
+void G_Checktrigger_stages( pTeam_t team, stage_t stage )
+{
+ int i;
+ gentity_t *ent;
+
+ for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( !Q_stricmp( ent->classname, "trigger_stage" ) )
+ {
+ if( team == ent->stageTeam && stage == ent->stageStage )
+ ent->use( ent, ent, ent );
+ }
+ }
+}
+
+
+/*
+===============
+trigger_stage_use
+===============
+*/
+void trigger_stage_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ G_UseTargets( self, self );
+}
+
+void SP_trigger_stage( gentity_t *self )
+{
+ G_SpawnInt( "team", "0", (int *)&self->stageTeam );
+ G_SpawnInt( "stage", "0", (int *)&self->stageStage );
+
+ self->use = trigger_stage_use;
+
+ self->r.svFlags = SVF_NOCLIENT;
+}
+
+
+/*
+===============
+trigger_win
+===============
+*/
+void trigger_win( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ G_UseTargets( self, self );
+}
+
+void SP_trigger_win( gentity_t *self )
+{
+ G_SpawnInt( "team", "0", (int *)&self->stageTeam );
+
+ self->use = trigger_win;
+
+ self->r.svFlags = SVF_NOCLIENT;
+}
+
+
+/*
+===============
+trigger_buildable_match
+===============
+*/
+qboolean trigger_buildable_match( gentity_t *self, gentity_t *activator )
+{
+ int i = 0;
+
+ //if there is no buildable list every buildable triggers
+ if( self->bTriggers[ i ] == BA_NONE )
+ return qtrue;
+ else
+ {
+ //otherwise check against the list
+ for( i = 0; self->bTriggers[ i ] != BA_NONE; i++ )
+ {
+ if( activator->s.modelindex == self->bTriggers[ i ] )
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+trigger_buildable_trigger
+===============
+*/
+void trigger_buildable_trigger( gentity_t *self, gentity_t *activator )
+{
+ self->activator = activator;
+
+ if( self->s.eFlags & EF_NODRAW )
+ return;
+
+ if( self->nextthink )
+ return; // can't retrigger until the wait is over
+
+ if( self->s.eFlags & EF_DEAD )
+ {
+ if( !trigger_buildable_match( self, activator ) )
+ G_UseTargets( self, activator );
+ }
+ else
+ {
+ if( trigger_buildable_match( self, activator ) )
+ G_UseTargets( self, activator );
+ }
+
+ if( self->wait > 0 )
+ {
+ self->think = multi_wait;
+ self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
+ }
+ else
+ {
+ // we can't just remove (self) here, because this is a touch function
+ // called while looping through area links...
+ self->touch = 0;
+ self->nextthink = level.time + FRAMETIME;
+ self->think = G_FreeEntity;
+ }
+}
+
+/*
+===============
+trigger_buildable_touch
+===============
+*/
+void trigger_buildable_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ //only triggered by buildables
+ if( other->s.eType != ET_BUILDABLE )
+ return;
+
+ trigger_buildable_trigger( ent, other );
+}
+
+/*
+===============
+trigger_buildable_use
+===============
+*/
+void trigger_buildable_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ ent->s.eFlags ^= EF_NODRAW;
+}
+
+/*
+===============
+SP_trigger_buildable
+===============
+*/
+void SP_trigger_buildable( gentity_t *self )
+{
+ char *buffer;
+
+ G_SpawnFloat( "wait", "0.5", &self->wait );
+ G_SpawnFloat( "random", "0", &self->random );
+
+ if( self->random >= self->wait && self->wait >= 0 )
+ {
+ self->random = self->wait - FRAMETIME;
+ G_Printf( S_COLOR_YELLOW "WARNING: trigger_buildable has random >= wait\n" );
+ }
+
+ G_SpawnString( "buildables", "", &buffer );
+
+ BG_ParseCSVBuildableList( buffer, self->bTriggers, BA_NUM_BUILDABLES );
+
+ self->touch = trigger_buildable_touch;
+ self->use = trigger_buildable_use;
+
+ // SPAWN_DISABLED
+ if( self->spawnflags & 1 )
+ self->s.eFlags |= EF_NODRAW;
+
+ // NEGATE
+ if( self->spawnflags & 2 )
+ self->s.eFlags |= EF_DEAD;
+
+ InitTrigger( self );
+ trap_LinkEntity( self );
+}
+
+
+/*
+===============
+trigger_class_match
+===============
+*/
+qboolean trigger_class_match( gentity_t *self, gentity_t *activator )
+{
+ int i = 0;
+
+ //if there is no class list every class triggers (stupid case)
+ if( self->cTriggers[ i ] == PCL_NONE )
+ return qtrue;
+ else
+ {
+ //otherwise check against the list
+ for( i = 0; self->cTriggers[ i ] != PCL_NONE; i++ )
+ {
+ if( activator->client->ps.stats[ STAT_PCLASS ] == self->cTriggers[ i ] )
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+trigger_class_trigger
+===============
+*/
+void trigger_class_trigger( gentity_t *self, gentity_t *activator )
+{
+ //sanity check
+ if( !activator->client )
+ return;
+
+ if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return;
+
+ if( self->s.eFlags & EF_NODRAW )
+ return;
+
+ self->activator = activator;
+ if( self->nextthink )
+ return; // can't retrigger until the wait is over
+
+ if( self->s.eFlags & EF_DEAD )
+ {
+ if( !trigger_class_match( self, activator ) )
+ G_UseTargets( self, activator );
+ }
+ else
+ {
+ if( trigger_class_match( self, activator ) )
+ G_UseTargets( self, activator );
+ }
+
+ if( self->wait > 0 )
+ {
+ self->think = multi_wait;
+ self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
+ }
+ else
+ {
+ // we can't just remove (self) here, because this is a touch function
+ // called while looping through area links...
+ self->touch = 0;
+ self->nextthink = level.time + FRAMETIME;
+ self->think = G_FreeEntity;
+ }
+}
+
+/*
+===============
+trigger_class_touch
+===============
+*/
+void trigger_class_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ //only triggered by clients
+ if( !other->client )
+ return;
+
+ trigger_class_trigger( ent, other );
+}
+
+/*
+===============
+trigger_class_use
+===============
+*/
+void trigger_class_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ ent->s.eFlags ^= EF_NODRAW;
+}
+
+/*
+===============
+SP_trigger_class
+===============
+*/
+void SP_trigger_class( gentity_t *self )
+{
+ char *buffer;
+
+ G_SpawnFloat( "wait", "0.5", &self->wait );
+ G_SpawnFloat( "random", "0", &self->random );
+
+ if( self->random >= self->wait && self->wait >= 0 )
+ {
+ self->random = self->wait - FRAMETIME;
+ G_Printf( S_COLOR_YELLOW "WARNING: trigger_class has random >= wait\n" );
+ }
+
+ G_SpawnString( "classes", "", &buffer );
+
+ BG_ParseCSVClassList( buffer, self->cTriggers, PCL_NUM_CLASSES );
+
+ self->touch = trigger_class_touch;
+ self->use = trigger_class_use;
+
+ // SPAWN_DISABLED
+ if( self->spawnflags & 1 )
+ self->s.eFlags |= EF_NODRAW;
+
+ // NEGATE
+ if( self->spawnflags & 2 )
+ self->s.eFlags |= EF_DEAD;
+
+ InitTrigger( self );
+ trap_LinkEntity( self );
+}
+
+
+/*
+===============
+trigger_equipment_match
+===============
+*/
+qboolean trigger_equipment_match( gentity_t *self, gentity_t *activator )
+{
+ int i = 0;
+
+ //if there is no equipment list all equipment triggers (stupid case)
+ if( self->wTriggers[ i ] == WP_NONE && self->uTriggers[ i ] == UP_NONE )
+ return qtrue;
+ else
+ {
+ //otherwise check against the lists
+ for( i = 0; self->wTriggers[ i ] != WP_NONE; i++ )
+ {
+ if( BG_InventoryContainsWeapon( self->wTriggers[ i ], activator->client->ps.stats ) )
+ return qtrue;
+ }
+
+ for( i = 0; self->uTriggers[ i ] != UP_NONE; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( self->uTriggers[ i ], activator->client->ps.stats ) )
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+trigger_equipment_trigger
+===============
+*/
+void trigger_equipment_trigger( gentity_t *self, gentity_t *activator )
+{
+ //sanity check
+ if( !activator->client )
+ return;
+
+ if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ if( self->s.eFlags & EF_NODRAW )
+ return;
+
+ self->activator = activator;
+ if( self->nextthink )
+ return; // can't retrigger until the wait is over
+
+ if( self->s.eFlags & EF_DEAD )
+ {
+ if( !trigger_equipment_match( self, activator ) )
+ G_UseTargets( self, activator );
+ }
+ else
+ {
+ if( trigger_equipment_match( self, activator ) )
+ G_UseTargets( self, activator );
+ }
+
+ if( self->wait > 0 )
+ {
+ self->think = multi_wait;
+ self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
+ }
+ else
+ {
+ // we can't just remove (self) here, because this is a touch function
+ // called while looping through area links...
+ self->touch = 0;
+ self->nextthink = level.time + FRAMETIME;
+ self->think = G_FreeEntity;
+ }
+}
+
+/*
+===============
+trigger_equipment_touch
+===============
+*/
+void trigger_equipment_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ //only triggered by clients
+ if( !other->client )
+ return;
+
+ trigger_equipment_trigger( ent, other );
+}
+
+/*
+===============
+trigger_equipment_use
+===============
+*/
+void trigger_equipment_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ ent->s.eFlags ^= EF_NODRAW;
+}
+
+/*
+===============
+SP_trigger_equipment
+===============
+*/
+void SP_trigger_equipment( gentity_t *self )
+{
+ char *buffer;
+
+ G_SpawnFloat( "wait", "0.5", &self->wait );
+ G_SpawnFloat( "random", "0", &self->random );
+
+ if( self->random >= self->wait && self->wait >= 0 )
+ {
+ self->random = self->wait - FRAMETIME;
+ G_Printf( S_COLOR_YELLOW "WARNING: trigger_equipment has random >= wait\n" );
+ }
+
+ G_SpawnString( "equipment", "", &buffer );
+
+ BG_ParseCSVEquipmentList( buffer, self->wTriggers, WP_NUM_WEAPONS,
+ self->uTriggers, UP_NUM_UPGRADES );
+
+ self->touch = trigger_equipment_touch;
+ self->use = trigger_equipment_use;
+
+ // SPAWN_DISABLED
+ if( self->spawnflags & 1 )
+ self->s.eFlags |= EF_NODRAW;
+
+ // NEGATE
+ if( self->spawnflags & 2 )
+ self->s.eFlags |= EF_DEAD;
+
+ InitTrigger( self );
+ trap_LinkEntity( self );
+}
+
+
+/*
+===============
+trigger_gravity_touch
+===============
+*/
+void trigger_gravity_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
+{
+ //only triggered by clients
+ if( !other->client )
+ return;
+
+ other->client->ps.gravity = ent->triggerGravity;
+}
+
+/*
+===============
+trigger_gravity_use
+===============
+*/
+void trigger_gravity_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
+{
+ if( ent->r.linked )
+ trap_UnlinkEntity( ent );
+ else
+ trap_LinkEntity( ent );
+}
+
+
+/*
+===============
+SP_trigger_gravity
+===============
+*/
+void SP_trigger_gravity( gentity_t *self )
+{
+ G_SpawnInt( "gravity", "800", &self->triggerGravity );
+
+ self->touch = trigger_gravity_touch;
+ self->use = trigger_gravity_use;
+
+ InitTrigger( self );
+ trap_LinkEntity( self );
+}
+
+
+/*
+===============
+trigger_heal_use
+===============
+*/
+void trigger_heal_use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( self->r.linked )
+ trap_UnlinkEntity( self );
+ else
+ trap_LinkEntity( self );
+}
+
+/*
+===============
+trigger_heal_touch
+===============
+*/
+void trigger_heal_touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ int max;
+
+ if( !other->client )
+ return;
+
+ if( self->timestamp > level.time )
+ return;
+
+ if( self->spawnflags & 2 )
+ self->timestamp = level.time + 1000;
+ else
+ self->timestamp = level.time + FRAMETIME;
+
+ max = other->client->ps.stats[ STAT_MAX_HEALTH ];
+
+ other->health += self->damage;
+
+ if( other->health > max )
+ other->health = max;
+
+ other->client->ps.stats[ STAT_HEALTH ] = other->health;
+}
+
+/*
+===============
+SP_trigger_heal
+===============
+*/
+void SP_trigger_heal( gentity_t *self )
+{
+ G_SpawnInt( "heal", "5", &self->damage );
+
+ if( self->damage <= 0 )
+ {
+ self->damage = 1;
+ G_Printf( S_COLOR_YELLOW "WARNING: trigger_heal with negative damage key\n" );
+ }
+
+ self->touch = trigger_heal_touch;
+ self->use = trigger_heal_use;
+
+ InitTrigger( self );
+
+ // link in to the world if starting active
+ if( !( self->spawnflags & 1 ) )
+ trap_LinkEntity( self );
+}
+
+
+/*
+===============
+trigger_ammo_touch
+===============
+*/
+void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ int ammo, clips, maxClips, maxAmmo;
+
+ if( !other->client )
+ return;
+
+ if( other->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ if( self->timestamp > level.time )
+ return;
+
+ if( other->client->ps.weaponstate != WEAPON_READY )
+ return;
+
+ if( BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 2 )
+ return;
+
+ if( !BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 4 )
+ return;
+
+ if( self->spawnflags & 1 )
+ self->timestamp = level.time + 1000;
+ else
+ self->timestamp = level.time + FRAMETIME;
+
+ BG_FindAmmoForWeapon( other->client->ps.weapon, &maxAmmo, &maxClips );
+ ammo = other->client->ps.ammo;
+ clips = other->client->ps.clips;
+
+ if( ( ammo + self->damage ) > maxAmmo )
+ {
+ if( clips < maxClips )
+ {
+ clips++;
+ ammo = 1;
+ }
+ else
+ ammo = maxAmmo;
+ }
+ else
+ ammo += self->damage;
+
+ other->client->ps.ammo = ammo;
+ other->client->ps.clips = clips;
+}
+
+/*
+===============
+SP_trigger_ammo
+===============
+*/
+void SP_trigger_ammo( gentity_t *self )
+{
+ G_SpawnInt( "ammo", "1", &self->damage );
+
+ if( self->damage <= 0 )
+ {
+ self->damage = 1;
+ G_Printf( S_COLOR_YELLOW "WARNING: trigger_ammo with negative ammo key\n" );
+ }
+
+ self->touch = trigger_ammo_touch;
+
+ InitTrigger( self );
+ trap_LinkEntity( self );
+}
diff --git a/src/game/g_utils.c b/src/game/g_utils.c
new file mode 100644
index 0000000..a74df3f
--- /dev/null
+++ b/src/game/g_utils.c
@@ -0,0 +1,849 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// g_utils.c -- misc utility functions for game module
+
+#include "g_local.h"
+
+typedef struct
+{
+ char oldShader[ MAX_QPATH ];
+ char newShader[ MAX_QPATH ];
+ float timeOffset;
+} shaderRemap_t;
+
+#define MAX_SHADER_REMAPS 128
+
+int remapCount = 0;
+shaderRemap_t remappedShaders[ MAX_SHADER_REMAPS ];
+
+void AddRemap( const char *oldShader, const char *newShader, float timeOffset )
+{
+ int i;
+
+ for( i = 0; i < remapCount; i++ )
+ {
+ if( Q_stricmp( oldShader, remappedShaders[ i ].oldShader ) == 0 )
+ {
+ // found it, just update this one
+ strcpy( remappedShaders[ i ].newShader,newShader );
+ remappedShaders[ i ].timeOffset = timeOffset;
+ return;
+ }
+ }
+
+ if( remapCount < MAX_SHADER_REMAPS )
+ {
+ strcpy( remappedShaders[ remapCount ].newShader,newShader );
+ strcpy( remappedShaders[ remapCount ].oldShader,oldShader );
+ remappedShaders[ remapCount ].timeOffset = timeOffset;
+ remapCount++;
+ }
+}
+
+const char *BuildShaderStateConfig( void )
+{
+ static char buff[ MAX_STRING_CHARS * 4 ];
+ char out[ ( MAX_QPATH * 2 ) + 5 ];
+ int i;
+
+ memset( buff, 0, MAX_STRING_CHARS );
+
+ for( i = 0; i < remapCount; i++ )
+ {
+ Com_sprintf( out, ( MAX_QPATH * 2 ) + 5, "%s=%s:%5.2f@", remappedShaders[ i ].oldShader,
+ remappedShaders[ i ].newShader, remappedShaders[ i ].timeOffset );
+ Q_strcat( buff, sizeof( buff ), out );
+ }
+ return buff;
+}
+
+
+/*
+=========================================================================
+
+model / sound configstring indexes
+
+=========================================================================
+*/
+
+/*
+================
+G_FindConfigstringIndex
+
+================
+*/
+int G_FindConfigstringIndex( char *name, int start, int max, qboolean create )
+{
+ int i;
+ char s[ MAX_STRING_CHARS ];
+
+ if( !name || !name[ 0 ] )
+ return 0;
+
+ for( i = 1; i < max; i++ )
+ {
+ trap_GetConfigstring( start + i, s, sizeof( s ) );
+ if( !s[ 0 ] )
+ break;
+
+ if( !strcmp( s, name ) )
+ return i;
+ }
+
+ if( !create )
+ return 0;
+
+ if( i == max )
+ G_Error( "G_FindConfigstringIndex: overflow" );
+
+ trap_SetConfigstring( start + i, name );
+
+ return i;
+}
+
+//TA: added ParticleSystemIndex
+int G_ParticleSystemIndex( char *name )
+{
+ return G_FindConfigstringIndex( name, CS_PARTICLE_SYSTEMS, MAX_GAME_PARTICLE_SYSTEMS, qtrue );
+}
+
+//TA: added ShaderIndex
+int G_ShaderIndex( char *name )
+{
+ return G_FindConfigstringIndex( name, CS_SHADERS, MAX_GAME_SHADERS, qtrue );
+}
+
+int G_ModelIndex( char *name )
+{
+ return G_FindConfigstringIndex( name, CS_MODELS, MAX_MODELS, qtrue );
+}
+
+int G_SoundIndex( char *name )
+{
+ return G_FindConfigstringIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue );
+}
+
+//=====================================================================
+
+
+/*
+================
+G_TeamCommand
+
+Broadcasts a command to only a specific team
+================
+*/
+void G_TeamCommand( pTeam_t team, char *cmd )
+{
+ int i;
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_CONNECTED )
+ {
+ if( level.clients[ i ].pers.teamSelection == team ||
+ ( level.clients[ i ].pers.teamSelection == PTE_NONE &&
+ G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) )
+ trap_SendServerCommand( i, cmd );
+ }
+ }
+}
+
+
+/*
+=============
+G_Find
+
+Searches all active entities for the next one that holds
+the matching string at fieldofs (use the FOFS() macro) in the structure.
+
+Searches beginning at the entity after from, or the beginning if NULL
+NULL will be returned if the end of the list is reached.
+
+=============
+*/
+gentity_t *G_Find( gentity_t *from, int fieldofs, const char *match )
+{
+ char *s;
+
+ if( !from )
+ from = g_entities;
+ else
+ from++;
+
+ for( ; from < &g_entities[ level.num_entities ]; from++ )
+ {
+ if( !from->inuse )
+ continue;
+ s = *(char **)( (byte *)from + fieldofs );
+
+ if( !s )
+ continue;
+
+ if( !Q_stricmp( s, match ) )
+ return from;
+ }
+
+ return NULL;
+}
+
+
+/*
+=============
+G_PickTarget
+
+Selects a random entity from among the targets
+=============
+*/
+#define MAXCHOICES 32
+
+gentity_t *G_PickTarget( char *targetname )
+{
+ gentity_t *ent = NULL;
+ int num_choices = 0;
+ gentity_t *choice[ MAXCHOICES ];
+
+ if( !targetname )
+ {
+ G_Printf("G_PickTarget called with NULL targetname\n");
+ return NULL;
+ }
+
+ while( 1 )
+ {
+ ent = G_Find( ent, FOFS( targetname ), targetname );
+
+ if( !ent )
+ break;
+
+ choice[ num_choices++ ] = ent;
+
+ if( num_choices == MAXCHOICES )
+ break;
+ }
+
+ if( !num_choices )
+ {
+ G_Printf( "G_PickTarget: target %s not found\n", targetname );
+ return NULL;
+ }
+
+ return choice[ rand( ) % num_choices ];
+}
+
+
+/*
+==============================
+G_UseTargets
+
+"activator" should be set to the entity that initiated the firing.
+
+Search for (string)targetname in all entities that
+match (string)self.target and call their .use function
+
+==============================
+*/
+void G_UseTargets( gentity_t *ent, gentity_t *activator )
+{
+ gentity_t *t;
+
+ if( !ent )
+ return;
+
+ if( ent->targetShaderName && ent->targetShaderNewName )
+ {
+ float f = level.time * 0.001;
+ AddRemap( ent->targetShaderName, ent->targetShaderNewName, f );
+ trap_SetConfigstring( CS_SHADERSTATE, BuildShaderStateConfig( ) );
+ }
+
+ if( !ent->target )
+ return;
+
+ t = NULL;
+ while( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL )
+ {
+ if( t == ent )
+ G_Printf( "WARNING: Entity used itself.\n" );
+ else
+ {
+ if( t->use )
+ t->use( t, ent, activator );
+ }
+
+ if( !ent->inuse )
+ {
+ G_Printf( "entity was removed while using targets\n" );
+ return;
+ }
+ }
+}
+
+
+/*
+=============
+TempVector
+
+This is just a convenience function
+for making temporary vectors for function calls
+=============
+*/
+float *tv( float x, float y, float z )
+{
+ static int index;
+ static vec3_t vecs[ 8 ];
+ float *v;
+
+ // use an array so that multiple tempvectors won't collide
+ // for a while
+ v = vecs[ index ];
+ index = ( index + 1 ) & 7;
+
+ v[ 0 ] = x;
+ v[ 1 ] = y;
+ v[ 2 ] = z;
+
+ return v;
+}
+
+
+/*
+=============
+VectorToString
+
+This is just a convenience function
+for printing vectors
+=============
+*/
+char *vtos( const vec3_t v )
+{
+ static int index;
+ static char str[ 8 ][ 32 ];
+ char *s;
+
+ // use an array so that multiple vtos won't collide
+ s = str[ index ];
+ index = ( index + 1 ) & 7;
+
+ Com_sprintf( s, 32, "(%i %i %i)", (int)v[ 0 ], (int)v[ 1 ], (int)v[ 2 ] );
+
+ return s;
+}
+
+
+/*
+===============
+G_SetMovedir
+
+The editor only specifies a single value for angles (yaw),
+but we have special constants to generate an up or down direction.
+Angles will be cleared, because it is being used to represent a direction
+instead of an orientation.
+===============
+*/
+void G_SetMovedir( vec3_t angles, vec3_t movedir )
+{
+ static vec3_t VEC_UP = { 0, -1, 0 };
+ static vec3_t MOVEDIR_UP = { 0, 0, 1 };
+ static vec3_t VEC_DOWN = { 0, -2, 0 };
+ static vec3_t MOVEDIR_DOWN = { 0, 0, -1 };
+
+ if( VectorCompare( angles, VEC_UP ) )
+ VectorCopy( MOVEDIR_UP, movedir );
+ else if( VectorCompare( angles, VEC_DOWN ) )
+ VectorCopy( MOVEDIR_DOWN, movedir );
+ else
+ AngleVectors( angles, movedir, NULL, NULL );
+
+ VectorClear( angles );
+}
+
+
+float vectoyaw( const vec3_t vec )
+{
+ float yaw;
+
+ if( vec[ YAW ] == 0 && vec[ PITCH ] == 0 )
+ {
+ yaw = 0;
+ }
+ else
+ {
+ if( vec[ PITCH ] )
+ yaw = ( atan2( vec[ YAW ], vec[ PITCH ] ) * 180 / M_PI );
+ else if( vec[ YAW ] > 0 )
+ yaw = 90;
+ else
+ yaw = 270;
+
+ if( yaw < 0 )
+ yaw += 360;
+ }
+
+ return yaw;
+}
+
+
+void G_InitGentity( gentity_t *e )
+{
+ e->inuse = qtrue;
+ e->classname = "noclass";
+ e->s.number = e - g_entities;
+ e->r.ownerNum = ENTITYNUM_NONE;
+}
+
+/*
+=================
+G_Spawn
+
+Either finds a free entity, or allocates a new one.
+
+ The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
+never be used by anything else.
+
+Try to avoid reusing an entity that was recently freed, because it
+can cause the client to think the entity morphed into something else
+instead of being removed and recreated, which can cause interpolated
+angles and bad trails.
+=================
+*/
+gentity_t *G_Spawn( void )
+{
+ int i, force;
+ gentity_t *e;
+
+ e = NULL; // shut up warning
+ i = 0; // shut up warning
+
+ for( force = 0; force < 2; force++ )
+ {
+ // if we go through all entities and can't find one to free,
+ // override the normal minimum times before use
+ e = &g_entities[ MAX_CLIENTS ];
+
+ for( i = MAX_CLIENTS; i < level.num_entities; i++, e++ )
+ {
+ if( e->inuse )
+ continue;
+
+ // the first couple seconds of server time can involve a lot of
+ // freeing and allocating, so relax the replacement policy
+ if( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 )
+ continue;
+
+ // reuse this slot
+ G_InitGentity( e );
+ return e;
+ }
+
+ if( i != MAX_GENTITIES )
+ break;
+ }
+
+ if( i == ENTITYNUM_MAX_NORMAL )
+ {
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ G_Printf( "%4i: %s\n", i, g_entities[ i ].classname );
+
+ G_Error( "G_Spawn: no free entities" );
+ }
+
+ // open up a new slot
+ level.num_entities++;
+
+ // let the server system know that there are more entities
+ trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
+ &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) );
+
+ G_InitGentity( e );
+ return e;
+}
+
+
+/*
+=================
+G_EntitiesFree
+=================
+*/
+qboolean G_EntitiesFree( void )
+{
+ int i;
+ gentity_t *e;
+
+ e = &g_entities[ MAX_CLIENTS ];
+
+ for( i = MAX_CLIENTS; i < level.num_entities; i++, e++ )
+ {
+ if( e->inuse )
+ continue;
+
+ // slot available
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+
+/*
+=================
+G_FreeEntity
+
+Marks the entity as free
+=================
+*/
+void G_FreeEntity( gentity_t *ent )
+{
+ trap_UnlinkEntity( ent ); // unlink from world
+
+ if( ent->neverFree )
+ return;
+
+ memset( ent, 0, sizeof( *ent ) );
+ ent->classname = "freent";
+ ent->freetime = level.time;
+ ent->inuse = qfalse;
+}
+
+/*
+=================
+G_TempEntity
+
+Spawns an event entity that will be auto-removed
+The origin will be snapped to save net bandwidth, so care
+must be taken if the origin is right on a surface (snap towards start vector first)
+=================
+*/
+gentity_t *G_TempEntity( vec3_t origin, int event )
+{
+ gentity_t *e;
+ vec3_t snapped;
+
+ e = G_Spawn( );
+ e->s.eType = ET_EVENTS + event;
+
+ e->classname = "tempEntity";
+ e->eventTime = level.time;
+ e->freeAfterEvent = qtrue;
+
+ VectorCopy( origin, snapped );
+ SnapVector( snapped ); // save network bandwidth
+ G_SetOrigin( e, snapped );
+
+ // find cluster for PVS
+ trap_LinkEntity( e );
+
+ return e;
+}
+
+
+
+/*
+==============================================================================
+
+Kill box
+
+==============================================================================
+*/
+
+/*
+=================
+G_KillBox
+
+Kills all entities that would touch the proposed new positioning
+of ent. Ent should be unlinked before calling this!
+=================
+*/
+void G_KillBox( gentity_t *ent )
+{
+ int i, num;
+ int touch[ MAX_GENTITIES ];
+ gentity_t *hit;
+ vec3_t mins, maxs;
+
+ VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
+ VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
+ num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
+
+ for( i = 0; i < num; i++ )
+ {
+ hit = &g_entities[ touch[ i ] ];
+
+ if( !hit->client )
+ continue;
+
+ //TA: impossible to telefrag self
+ if( ent == hit )
+ continue;
+
+ // nail it
+ G_Damage( hit, ent, ent, NULL, NULL,
+ 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
+ }
+
+}
+
+//==============================================================================
+
+/*
+===============
+G_AddPredictableEvent
+
+Use for non-pmove events that would also be predicted on the
+client side: jumppads and item pickups
+Adds an event+parm and twiddles the event counter
+===============
+*/
+void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm )
+{
+ if( !ent->client )
+ return;
+
+ BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps );
+}
+
+
+/*
+===============
+G_AddEvent
+
+Adds an event+parm and twiddles the event counter
+===============
+*/
+void G_AddEvent( gentity_t *ent, int event, int eventParm )
+{
+ int bits;
+
+ if( !event )
+ {
+ G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
+ return;
+ }
+
+ // eventParm is converted to uint8_t (0 - 255) in msg.c
+ if( eventParm & ~0xFF )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: G_AddEvent: event %d "
+ " eventParm uint8_t overflow (given %d)\n", event, eventParm );
+ }
+
+ // clients need to add the event in playerState_t instead of entityState_t
+ if( ent->client )
+ {
+ bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
+ bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
+ ent->client->ps.externalEvent = event | bits;
+ ent->client->ps.externalEventParm = eventParm;
+ ent->client->ps.externalEventTime = level.time;
+ }
+ else
+ {
+ bits = ent->s.event & EV_EVENT_BITS;
+ bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
+ ent->s.event = event | bits;
+ ent->s.eventParm = eventParm;
+ }
+
+ ent->eventTime = level.time;
+}
+
+
+/*
+===============
+G_BroadcastEvent
+
+Sends an event to every client
+===============
+*/
+void G_BroadcastEvent( int event, int eventParm )
+{
+ gentity_t *ent;
+
+ ent = G_TempEntity( vec3_origin, event );
+ ent->s.eventParm = eventParm;
+ ent->r.svFlags = SVF_BROADCAST; // send to everyone
+}
+
+
+/*
+=============
+G_Sound
+=============
+*/
+void G_Sound( gentity_t *ent, int channel, int soundIndex )
+{
+ gentity_t *te;
+
+ te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND );
+ te->s.eventParm = soundIndex;
+}
+
+
+/*
+=============
+G_ClientIsLagging
+=============
+*/
+qboolean G_ClientIsLagging( gclient_t *client )
+{
+ if( client )
+ {
+ if( client->ps.ping >= 999 )
+ return qtrue;
+ else
+ return qfalse;
+ }
+
+ return qfalse; //is a non-existant client lagging? woooo zen
+}
+
+//==============================================================================
+
+
+/*
+================
+G_SetOrigin
+
+Sets the pos trajectory for a fixed position
+================
+*/
+void G_SetOrigin( gentity_t *ent, vec3_t origin )
+{
+ VectorCopy( origin, ent->s.pos.trBase );
+ ent->s.pos.trType = TR_STATIONARY;
+ ent->s.pos.trTime = 0;
+ ent->s.pos.trDuration = 0;
+ VectorClear( ent->s.pos.trDelta );
+
+ VectorCopy( origin, ent->r.currentOrigin );
+ VectorCopy( origin, ent->s.origin ); //TA: if shit breaks - blame this line
+}
+
+//TA: from quakestyle.telefragged.com
+// (NOBODY): Code helper function
+//
+gentity_t *G_FindRadius( gentity_t *from, vec3_t org, float rad )
+{
+ vec3_t eorg;
+ int j;
+
+ if( !from )
+ from = g_entities;
+ else
+ from++;
+
+ for( ; from < &g_entities[ level.num_entities ]; from++ )
+ {
+ if( !from->inuse )
+ continue;
+
+ for( j = 0; j < 3; j++ )
+ eorg[ j ] = org[ j ] - ( from->r.currentOrigin[ j ] + ( from->r.mins[ j ] + from->r.maxs[ j ] ) * 0.5 );
+
+ if( VectorLength( eorg ) > rad )
+ continue;
+
+ return from;
+ }
+
+ return NULL;
+}
+
+/*
+===============
+G_Visible
+
+Test for a LOS between two entities
+===============
+*/
+qboolean G_Visible( gentity_t *ent1, gentity_t *ent2 )
+{
+ trace_t trace;
+
+ trap_Trace( &trace, ent1->s.pos.trBase, NULL, NULL, ent2->s.pos.trBase, ent1->s.number, MASK_SHOT );
+
+ if( trace.contents & CONTENTS_SOLID )
+ return qfalse;
+
+ return qtrue;
+}
+
+/*
+===============
+G_ClosestEnt
+
+Test a list of entities for the closest to a particular point
+===============
+*/
+gentity_t *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities )
+{
+ int i;
+ float nd, d = 1000000.0f;
+ gentity_t *closestEnt = NULL;
+
+ for( i = 0; i < numEntities; i++ )
+ {
+ gentity_t *ent = entities[ i ];
+
+ nd = DistanceSquared( origin, ent->s.origin );
+ if( i == 0 || nd < d )
+ {
+ d = nd;
+ closestEnt = ent;
+ }
+ }
+
+ return closestEnt;
+}
+
+/*
+===============
+G_TriggerMenu
+
+Trigger a menu on some client
+===============
+*/
+void G_TriggerMenu( int clientNum, dynMenu_t menu )
+{
+ char buffer[ 32 ];
+
+ Com_sprintf( buffer, 32, "servermenu %d", menu );
+ trap_SendServerCommand( clientNum, buffer );
+}
+
+
+/*
+===============
+G_CloseMenus
+
+Close all open menus on some client
+===============
+*/
+void G_CloseMenus( int clientNum )
+{
+ char buffer[ 32 ];
+
+ Com_sprintf( buffer, 32, "serverclosemenus" );
+ trap_SendServerCommand( clientNum, buffer );
+}
diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c
new file mode 100644
index 0000000..84208f5
--- /dev/null
+++ b/src/game/g_weapon.c
@@ -0,0 +1,1661 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// g_weapon.c
+// perform the server side effects of a weapon firing
+
+#include "g_local.h"
+
+static vec3_t forward, right, up;
+static vec3_t muzzle;
+
+/*
+================
+G_ForceWeaponChange
+================
+*/
+void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon )
+{
+ int i;
+
+ if( !ent )
+ return;
+
+ if( ent->client->ps.weaponstate == WEAPON_RELOADING )
+ {
+ ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE;
+ ent->client->ps.weaponTime = 250;
+ ent->client->ps.weaponstate = WEAPON_READY;
+ }
+
+ ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH;
+
+ if( weapon == WP_NONE
+ || !BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ))
+ {
+ //switch to the first non blaster weapon
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( i == WP_BLASTER )
+ continue;
+
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) )
+ {
+ ent->client->ps.persistant[ PERS_NEWWEAPON ] = i;
+ break;
+ }
+ }
+
+ //only got the blaster to switch to
+ if( i == WP_NUM_WEAPONS )
+ ent->client->ps.persistant[ PERS_NEWWEAPON ] = WP_BLASTER;
+ }
+ else
+ ent->client->ps.persistant[ PERS_NEWWEAPON ] = weapon;
+
+ // Lak: The following hack has been moved to PM_BeginWeaponChange, but I'm going to
+ // redundantly leave it here as well just in case there's a case I'm forgetting
+ // because I don't want to face the gameplay consequences such an error would have
+
+ // force this here to prevent flamer effect from continuing
+ ent->client->ps.generic1 = WPM_NOTFIRING;
+
+ ent->client->ps.weapon = ent->client->ps.persistant[ PERS_NEWWEAPON ];
+}
+
+/*
+=================
+G_GiveClientMaxAmmo
+=================
+*/
+void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo )
+{
+ int i;
+ int maxAmmo, maxClips;
+ qboolean weaponType, restoredAmmo = qfalse;
+
+ // GH FIXME
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( buyingEnergyAmmo )
+ weaponType = BG_FindUsesEnergyForWeapon( i );
+ else
+ weaponType = !BG_FindUsesEnergyForWeapon( i );
+
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) &&
+ weaponType && !BG_FindInfinteAmmoForWeapon( i ) &&
+ !BG_WeaponIsFull( i, ent->client->ps.stats,
+ ent->client->ps.ammo, ent->client->ps.clips ) )
+ {
+ BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips );
+
+ if( buyingEnergyAmmo )
+ {
+ G_AddEvent( ent, EV_RPTUSE_SOUND, 0 );
+
+ if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
+ maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
+ }
+
+ ent->client->ps.ammo = maxAmmo;
+ ent->client->ps.clips = maxClips;
+
+ restoredAmmo = qtrue;
+ }
+ }
+
+ if( restoredAmmo )
+ G_ForceWeaponChange( ent, ent->client->ps.weapon );
+}
+
+/*
+================
+G_BounceProjectile
+================
+*/
+void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout )
+{
+ vec3_t v, newv;
+ float dot;
+
+ VectorSubtract( impact, start, v );
+ dot = DotProduct( v, dir );
+ VectorMA( v, -2 * dot, dir, newv );
+
+ VectorNormalize(newv);
+ VectorMA(impact, 8192, newv, endout);
+}
+
+/*
+================
+G_WideTrace
+
+Trace a bounding box against entities, but not the world
+Also check there is a line of sight between the start and end point
+================
+*/
+static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, float width, gentity_t **target )
+{
+ vec3_t mins, maxs;
+ vec3_t end;
+
+ VectorSet( mins, -width, -width, -width );
+ VectorSet( maxs, width, width, width );
+
+ *target = NULL;
+
+ if( !ent->client )
+ return;
+
+ // Set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+ VectorMA( muzzle, range, forward, end );
+
+ G_UnlaggedOn( ent, muzzle, range );
+
+ // Trace against entities
+ trap_Trace( tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY );
+ if( tr->entityNum != ENTITYNUM_NONE )
+ {
+ *target = &g_entities[ tr->entityNum ];
+
+ // Set range to the trace length plus the width, so that the end of the
+ // LOS trace is close to the exterior of the target's bounding box
+ range = Distance( muzzle, tr->endpos ) + width;
+ VectorMA( muzzle, range, forward, end );
+
+ // Trace for line of sight against the world
+ trap_Trace( tr, muzzle, NULL, NULL, end, 0, CONTENTS_SOLID );
+ if( tr->fraction < 1.0f )
+ *target = NULL;
+ }
+
+ G_UnlaggedOff( );
+}
+
+
+/*
+======================
+SnapVectorTowards
+
+Round a vector to integers for more efficient network
+transmission, but make sure that it rounds towards a given point
+rather than blindly truncating. This prevents it from truncating
+into a wall.
+======================
+*/
+void SnapVectorTowards( vec3_t v, vec3_t to )
+{
+ int i;
+
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ if( to[ i ] <= v[ i ] )
+ v[ i ] = (int)v[ i ];
+ else
+ v[ i ] = (int)v[ i ] + 1;
+ }
+}
+
+/*
+===============
+meleeAttack
+===============
+*/
+void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod )
+{
+ trace_t tr;
+ vec3_t end;
+ gentity_t *tent;
+ gentity_t *traceEnt;
+ vec3_t mins, maxs;
+
+ VectorSet( mins, -width, -width, -width );
+ VectorSet( maxs, width, width, width );
+
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+
+ VectorMA( muzzle, range, forward, end );
+
+ G_UnlaggedOn( ent, muzzle, range );
+ trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
+ G_UnlaggedOff( );
+
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ // send blood impact
+ if( traceEnt->takedamage && traceEnt->client )
+ {
+ tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = traceEnt->s.number;
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+
+ if( traceEnt->takedamage )
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod );
+}
+
+/*
+======================================================================
+
+MACHINEGUN
+
+======================================================================
+*/
+
+void bulletFire( gentity_t *ent, float spread, int damage, int mod )
+{
+ trace_t tr;
+ vec3_t end;
+ float r;
+ float u;
+ gentity_t *tent;
+ gentity_t *traceEnt;
+
+ r = random( ) * M_PI * 2.0f;
+ u = sin( r ) * crandom( ) * spread * 16;
+ r = cos( r ) * crandom( ) * spread * 16;
+ VectorMA( muzzle, 8192 * 16, forward, end );
+ VectorMA( end, r, right, end );
+ VectorMA( end, u, up, end );
+
+ // don't use unlagged if this is not a client (e.g. turret)
+ if( ent->client )
+ {
+ G_UnlaggedOn( ent, muzzle, 8192 * 16 );
+ trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
+ G_UnlaggedOff( );
+ }
+ else
+ trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
+
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ // snap the endpos to integers, but nudged towards the line
+ SnapVectorTowards( tr.endpos, muzzle );
+
+ // send bullet impact
+ if( traceEnt->takedamage && traceEnt->client )
+ {
+ tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
+ tent->s.eventParm = traceEnt->s.number;
+ }
+ else
+ {
+ tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ }
+ tent->s.otherEntityNum = ent->s.number;
+
+ if( traceEnt->takedamage )
+ {
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos,
+ damage, 0, mod );
+ }
+}
+
+/*
+======================================================================
+
+SHOTGUN
+
+======================================================================
+*/
+
+// this should match CG_ShotgunPattern
+void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent )
+{
+ int i;
+ float r, u;
+ vec3_t end;
+ vec3_t forward, right, up;
+ trace_t tr;
+ gentity_t *traceEnt;
+
+ // derive the right and up vectors from the forward vector, because
+ // the client won't have any other information
+ VectorNormalize2( origin2, forward );
+ PerpendicularVector( right, forward );
+ CrossProduct( forward, right, up );
+
+ // generate the "random" spread pattern
+ for( i = 0; i < SHOTGUN_PELLETS; i++ )
+ {
+ r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
+ u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
+ VectorMA( origin, 8192 * 16, forward, end );
+ VectorMA( end, r, right, end );
+ VectorMA( end, u, up, end );
+
+ trap_Trace( &tr, origin, NULL, NULL, end, ent->s.number, MASK_SHOT );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ // send bullet impact
+ if( !( tr.surfaceFlags & SURF_NOIMPACT ) )
+ {
+ if( traceEnt->takedamage )
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, SHOTGUN_DMG, 0, MOD_SHOTGUN );
+ }
+ }
+}
+
+
+void shotgunFire( gentity_t *ent )
+{
+ gentity_t *tent;
+
+ // send shotgun blast
+ tent = G_TempEntity( muzzle, EV_SHOTGUN );
+ VectorScale( forward, 4096, tent->s.origin2 );
+ SnapVector( tent->s.origin2 );
+ tent->s.eventParm = rand() & 255; // seed for spread pattern
+ tent->s.otherEntityNum = ent->s.number;
+ G_UnlaggedOn( ent, muzzle, 8192 * 16 );
+ ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
+ G_UnlaggedOff();
+}
+
+/*
+======================================================================
+
+MASS DRIVER
+
+======================================================================
+*/
+
+void massDriverFire( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t end;
+ gentity_t *tent;
+ gentity_t *traceEnt;
+
+ VectorMA( muzzle, 8192 * 16, forward, end );
+
+ G_UnlaggedOn( ent, muzzle, 8192 * 16 );
+ trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
+ G_UnlaggedOff( );
+
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ // snap the endpos to integers, but nudged towards the line
+ SnapVectorTowards( tr.endpos, muzzle );
+
+ // send impact
+ if( traceEnt->takedamage && traceEnt->client )
+ {
+ tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = traceEnt->s.number;
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+ else
+ {
+ tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+
+ if( traceEnt->takedamage )
+ {
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos,
+ MDRIVER_DMG, 0, MOD_MDRIVER );
+ }
+}
+
+/*
+======================================================================
+
+LOCKBLOB
+
+======================================================================
+*/
+
+void lockBlobLauncherFire( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = fire_lockblob( ent, muzzle, forward );
+
+// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
+}
+
+/*
+======================================================================
+
+HIVE
+
+======================================================================
+*/
+
+void hiveFire( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = fire_hive( ent, muzzle, forward );
+
+// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
+}
+
+/*
+======================================================================
+
+BLASTER PISTOL
+
+======================================================================
+*/
+
+void blasterFire( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = fire_blaster( ent, muzzle, forward );
+
+// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
+}
+
+/*
+======================================================================
+
+PULSE RIFLE
+
+======================================================================
+*/
+
+void pulseRifleFire( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = fire_pulseRifle( ent, muzzle, forward );
+
+// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
+}
+
+/*
+======================================================================
+
+FLAME THROWER
+
+======================================================================
+*/
+
+void flamerFire( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = fire_flamer( ent, muzzle, forward );
+}
+
+/*
+======================================================================
+
+GRENADE
+
+======================================================================
+*/
+
+void throwGrenade( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = launch_grenade( ent, muzzle, forward );
+}
+
+/*
+======================================================================
+
+LAS GUN
+
+======================================================================
+*/
+
+/*
+===============
+lasGunFire
+===============
+*/
+void lasGunFire( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t end;
+ gentity_t *tent;
+ gentity_t *traceEnt;
+
+ VectorMA( muzzle, 8192 * 16, forward, end );
+
+ G_UnlaggedOn( ent, muzzle, 8192 * 16 );
+ trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
+ G_UnlaggedOff( );
+
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ // snap the endpos to integers, but nudged towards the line
+ SnapVectorTowards( tr.endpos, muzzle );
+
+ // send impact
+ if( traceEnt->takedamage && traceEnt->client )
+ {
+ tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = traceEnt->s.number;
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+ else
+ {
+ tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+
+ if( traceEnt->takedamage )
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, LASGUN_DAMAGE, 0, MOD_LASGUN );
+}
+
+/*
+======================================================================
+
+PAIN SAW
+
+======================================================================
+*/
+
+void painSawFire( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t end;
+ gentity_t *tent;
+ gentity_t *traceEnt;
+
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+
+ VectorMA( muzzle, PAINSAW_RANGE, forward, end );
+
+ G_UnlaggedOn( ent, muzzle, PAINSAW_RANGE );
+ trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
+ G_UnlaggedOff( );
+
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ // send blood impact
+ if( traceEnt->takedamage )
+ {
+ vec3_t temp;
+
+ //hack to get the particle system to line up with the weapon
+ VectorCopy( tr.endpos, temp );
+ temp[ 2 ] -= 10.0f;
+
+ if( traceEnt->client )
+ {
+ tent = G_TempEntity( temp, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = traceEnt->s.number;
+ }
+ else
+ tent = G_TempEntity( temp, EV_MISSILE_MISS );
+
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+
+ if( traceEnt->takedamage )
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW );
+}
+
+/*
+======================================================================
+
+LUCIFER CANNON
+
+======================================================================
+*/
+
+/*
+===============
+LCChargeFire
+===============
+*/
+void LCChargeFire( gentity_t *ent, qboolean secondary )
+{
+ gentity_t *m;
+
+ if( secondary )
+ {
+ m = fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE,
+ LCANNON_SECONDARY_RADIUS );
+ ent->client->ps.weaponTime = LCANNON_REPEAT;
+ }
+ else
+ {
+ m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS );
+ ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT;
+ }
+
+ ent->client->ps.stats[ STAT_MISC ] = 0;
+}
+
+/*
+======================================================================
+
+TESLA GENERATOR
+
+======================================================================
+*/
+
+
+void teslaFire( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t end;
+ gentity_t *traceEnt, *tent;
+
+ VectorMA( muzzle, TESLAGEN_RANGE, forward, end );
+
+ trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
+
+ if( tr.entityNum == ENTITYNUM_NONE )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( !traceEnt->client )
+ return;
+
+ if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return;
+
+ //so the client side knows
+ ent->s.eFlags |= EF_FIRING;
+
+ if( traceEnt->takedamage )
+ {
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos,
+ TESLAGEN_DMG, 0, MOD_TESLAGEN );
+ }
+
+ // snap the endpos to integers to save net bandwidth, but nudged towards the line
+ SnapVectorTowards( tr.endpos, muzzle );
+
+ // send railgun beam effect
+ tent = G_TempEntity( tr.endpos, EV_TESLATRAIL );
+
+ VectorCopy( muzzle, tent->s.origin2 );
+
+ tent->s.generic1 = ent->s.number; //src
+ tent->s.clientNum = traceEnt->s.number; //dest
+
+ // move origin a bit to come closer to the drawn gun muzzle
+ VectorMA( tent->s.origin2, 28, up, tent->s.origin2 );
+}
+
+
+/*
+======================================================================
+
+BUILD GUN
+
+======================================================================
+*/
+
+/*
+===============
+cancelBuildFire
+===============
+*/
+void cancelBuildFire( gentity_t *ent )
+{
+ vec3_t forward, end;
+ trace_t tr;
+ gentity_t *traceEnt;
+ int bHealth;
+
+ if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE )
+ {
+ ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
+ return;
+ }
+
+ //repair buildable
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
+ VectorMA( ent->client->ps.origin, 100, forward, end );
+
+ trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( tr.fraction < 1.0 &&
+ ( traceEnt->s.eType == ET_BUILDABLE ) &&
+ ( traceEnt->biteam == ent->client->ps.stats[ STAT_PTEAM ] ) &&
+ ( ( ent->client->ps.weapon >= WP_HBUILD2 ) &&
+ ( ent->client->ps.weapon <= WP_HBUILD ) ) &&
+ traceEnt->spawned && traceEnt->health > 0 )
+ {
+ if( ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
+ return;
+ }
+
+ bHealth = BG_FindHealthForBuildable( traceEnt->s.modelindex );
+
+ traceEnt->health += HBUILD_HEALRATE;
+ ent->client->pers.statscounters.repairspoisons++;
+ level.humanStatsCounters.repairspoisons++;
+
+ if( traceEnt->health > bHealth )
+ traceEnt->health = bHealth;
+
+ if( traceEnt->health == bHealth )
+ G_AddEvent( ent, EV_BUILD_REPAIRED, 0 );
+ else
+ G_AddEvent( ent, EV_BUILD_REPAIR, 0 );
+ }
+ }
+ else if( ent->client->ps.weapon == WP_ABUILD2 )
+ meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH,
+ ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); //melee attack for alien builder
+}
+
+/*
+===============
+buildFire
+===============
+*/
+void buildFire( gentity_t *ent, dynMenu_t menu )
+{
+ if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
+ {
+ if( ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
+ return;
+ }
+
+ if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) )
+ {
+ if( g_cheats.integer )
+ {
+ ent->client->ps.stats[ STAT_MISC ] = 0;
+ }
+ else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) )
+ {
+ ent->client->ps.stats[ STAT_MISC ] +=
+ BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
+ }
+ else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_IsPowered( muzzle ) &&
+ ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack
+ {
+ ent->client->ps.stats[ STAT_MISC ] +=
+ BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
+ }
+ else
+ ent->client->ps.stats[ STAT_MISC ] +=
+ BG_FindBuildDelayForWeapon( ent->s.weapon );
+
+ ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
+
+ // don't want it bigger than 32k
+ if( ent->client->ps.stats[ STAT_MISC ] > 30000 )
+ ent->client->ps.stats[ STAT_MISC ] = 30000;
+ }
+ return;
+ }
+
+ G_TriggerMenu( ent->client->ps.clientNum, menu );
+}
+
+void slowBlobFire( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = fire_slowBlob( ent, muzzle, forward );
+
+// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
+}
+
+
+/*
+======================================================================
+
+LEVEL0
+
+======================================================================
+*/
+
+/*
+===============
+CheckVenomAttack
+===============
+*/
+qboolean CheckVenomAttack( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t end;
+ gentity_t *tent;
+ gentity_t *traceEnt;
+ vec3_t mins, maxs;
+ int damage = LEVEL0_BITE_DMG;
+
+ VectorSet( mins, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH );
+ VectorSet( maxs, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH );
+
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+
+ VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end );
+
+ G_UnlaggedOn( ent, muzzle, LEVEL0_BITE_RANGE );
+ trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
+ G_UnlaggedOff( );
+
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return qfalse;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( !traceEnt->takedamage )
+ return qfalse;
+
+ //allow bites to work against defensive buildables only
+ if( traceEnt->s.eType == ET_BUILDABLE )
+ {
+ if( traceEnt->s.modelindex != BA_H_MGTURRET &&
+ traceEnt->s.modelindex != BA_H_TESLAGEN )
+ return qfalse;
+
+ //hackery
+ damage *= 0.5f;
+ }
+
+ if( traceEnt->client )
+ {
+ if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ return qfalse;
+ if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )
+ return qfalse;
+ }
+
+ // send blood impact
+ if( traceEnt->takedamage && traceEnt->client )
+ {
+ tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = traceEnt->s.number;
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE );
+
+ return qtrue;
+}
+
+/*
+======================================================================
+
+LEVEL1
+
+======================================================================
+*/
+
+/*
+===============
+CheckGrabAttack
+===============
+*/
+void CheckGrabAttack( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t end, dir;
+ gentity_t *traceEnt;
+
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+
+ VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end );
+
+ trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( !traceEnt->takedamage )
+ return;
+
+ if( traceEnt->client )
+ {
+ if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ return;
+
+ if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ if( !( traceEnt->client->ps.stats[ STAT_STATE ] & SS_GRABBED ) )
+ {
+ AngleVectors( traceEnt->client->ps.viewangles, dir, NULL, NULL );
+ traceEnt->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir );
+
+ //event for client side grab effect
+ G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 );
+ }
+
+ traceEnt->client->ps.stats[ STAT_STATE ] |= SS_GRABBED;
+
+ if( ent->client->ps.weapon == WP_ALEVEL1 )
+ traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_TIME;
+ else if( ent->client->ps.weapon == WP_ALEVEL1_UPG )
+ traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME;
+ }
+ else if( traceEnt->s.eType == ET_BUILDABLE &&
+ traceEnt->s.modelindex == BA_H_MGTURRET )
+ {
+ if( !traceEnt->lev1Grabbed )
+ G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 );
+
+ traceEnt->lev1Grabbed = qtrue;
+ traceEnt->lev1GrabTime = level.time;
+ }
+}
+
+/*
+===============
+poisonCloud
+===============
+*/
+void poisonCloud( gentity_t *ent )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *humanPlayer;
+ trace_t tr;
+
+ VectorAdd( ent->client->ps.origin, range, maxs );
+ VectorSubtract( ent->client->ps.origin, range, mins );
+
+ G_UnlaggedOn( ent, ent->client->ps.origin, LEVEL1_PCLOUD_RANGE );
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ humanPlayer = &g_entities[ entityList[ i ] ];
+
+ if( humanPlayer->client && humanPlayer->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, humanPlayer->client->ps.stats ) )
+ continue;
+
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, humanPlayer->client->ps.stats ) )
+ continue;
+
+ trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->s.origin, humanPlayer->s.number, MASK_SHOT );
+
+ //can't see target from here
+ if( tr.entityNum == ENTITYNUM_WORLD )
+ continue;
+
+ if( !( humanPlayer->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) )
+ {
+ humanPlayer->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED;
+ humanPlayer->client->lastPoisonCloudedTime = level.time;
+ humanPlayer->client->lastPoisonCloudedClient = ent;
+ trap_SendServerCommand( humanPlayer->client->ps.clientNum, "poisoncloud" );
+ }
+ }
+ }
+ G_UnlaggedOff( );
+}
+
+
+/*
+======================================================================
+
+LEVEL2
+
+======================================================================
+*/
+
+#define MAX_ZAPS 64
+
+static zap_t zaps[ MAX_CLIENTS ];
+
+/*
+===============
+G_FindNewZapTarget
+===============
+*/
+static gentity_t *G_FindNewZapTarget( gentity_t *ent )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE };
+ vec3_t mins, maxs;
+ int i, j, k, num;
+ gentity_t *enemy;
+ trace_t tr;
+
+ VectorScale( range, 1.0f / M_ROOT3, range );
+ VectorAdd( ent->s.origin, range, maxs );
+ VectorSubtract( ent->s.origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( ( ( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
+ ( enemy->s.eType == ET_BUILDABLE &&
+ BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) && enemy->health > 0 )
+ {
+ qboolean foundOldTarget = qfalse;
+
+ trap_Trace( &tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT );
+
+ //can't see target from here
+ if( tr.entityNum == ENTITYNUM_WORLD )
+ continue;
+
+ for( j = 0; j < MAX_ZAPS; j++ )
+ {
+ zap_t *zap = &zaps[ j ];
+
+ for( k = 0; k < zap->numTargets; k++ )
+ {
+ if( zap->targets[ k ] == enemy )
+ {
+ foundOldTarget = qtrue;
+ break;
+ }
+ }
+
+ if( foundOldTarget )
+ break;
+ }
+
+ // enemy is already targetted
+ if( foundOldTarget )
+ continue;
+
+ return enemy;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+===============
+G_UpdateZapEffect
+===============
+*/
+static void G_UpdateZapEffect( zap_t *zap )
+{
+ int j;
+ gentity_t *effect = zap->effectChannel;
+
+ effect->s.eType = ET_LEV2_ZAP_CHAIN;
+ effect->classname = "lev2zapchain";
+ G_SetOrigin( effect, zap->creator->s.origin );
+ effect->s.misc = zap->creator->s.number;
+
+ effect->s.time = effect->s.time2 = effect->s.constantLight = -1;
+
+ for( j = 0; j < zap->numTargets; j++ )
+ {
+ int number = zap->targets[ j ]->s.number;
+
+ switch( j )
+ {
+ case 0: effect->s.time = number; break;
+ case 1: effect->s.time2 = number; break;
+ case 2: effect->s.constantLight = number; break;
+ default: break;
+ }
+ }
+
+ trap_LinkEntity( effect );
+}
+
+/*
+===============
+G_CreateNewZap
+===============
+*/
+static void G_CreateNewZap( gentity_t *creator, gentity_t *target )
+{
+ int i, j;
+ zap_t *zap;
+
+ for( i = 0; i < MAX_ZAPS; i++ )
+ {
+ zap = &zaps[ i ];
+
+ if( !zap->used )
+ {
+ zap->used = qtrue;
+
+ zap->timeToLive = LEVEL2_AREAZAP_TIME;
+ zap->damageUsed = 0;
+
+ zap->creator = creator;
+
+ zap->targets[ 0 ] = target;
+ zap->numTargets = 1;
+
+ for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ )
+ {
+ zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] );
+
+ if( zap->targets[ j ] )
+ zap->numTargets++;
+ }
+
+ zap->effectChannel = G_Spawn( );
+ G_UpdateZapEffect( zap );
+
+ return;
+ }
+ }
+}
+
+
+/*
+===============
+G_UpdateZaps
+===============
+*/
+void G_UpdateZaps( int msec )
+{
+ int i, j;
+ zap_t *zap;
+ int damage;
+
+ for( i = 0; i < MAX_ZAPS; i++ )
+ {
+ zap = &zaps[ i ];
+
+ if( zap->used )
+ {
+ //check each target is valid
+ for( j = 0; j < zap->numTargets; j++ )
+ {
+ gentity_t *source;
+ gentity_t *target = zap->targets[ j ];
+
+ if( j == 0 )
+ source = zap->creator;
+ else
+ source = zap->targets[ j - 1 ];
+
+ if( target->health <= 0 || !target->inuse || //early out
+ Distance( source->s.origin, target->s.origin ) > LEVEL2_AREAZAP_RANGE )
+ {
+ target = zap->targets[ j ] = G_FindNewZapTarget( source );
+
+ //couldn't find a target, so forget about the rest of the chain
+ if( !target )
+ zap->numTargets = j;
+ }
+ }
+
+ if( zap->numTargets )
+ {
+ for( j = 0; j < zap->numTargets; j++ )
+ {
+ gentity_t *source;
+ gentity_t *target = zap->targets[ j ];
+ float r = 1.0f / zap->numTargets;
+ float damageFraction = 2 * r - 2 * j * r * r - r * r;
+ vec3_t forward;
+
+ if( j == 0 )
+ source = zap->creator;
+ else
+ source = zap->targets[ j - 1 ];
+
+ damage = ceil( ( (float)msec / LEVEL2_AREAZAP_TIME ) *
+ LEVEL2_AREAZAP_DMG * damageFraction );
+
+ // don't let a high msec value inflate the total damage
+ if( damage + zap->damageUsed > LEVEL2_AREAZAP_DMG )
+ damage = LEVEL2_AREAZAP_DMG - zap->damageUsed;
+
+ VectorSubtract( target->s.origin, source->s.origin, forward );
+ VectorNormalize( forward );
+
+ //do the damage
+ if( damage )
+ {
+ G_Damage( target, source, zap->creator, forward, target->s.origin,
+ damage, DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP );
+ zap->damageUsed += damage;
+ }
+ }
+ }
+
+ G_UpdateZapEffect( zap );
+
+ zap->timeToLive -= msec;
+
+ if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 )
+ {
+ zap->used = qfalse;
+ G_FreeEntity( zap->effectChannel );
+ }
+ }
+ }
+}
+
+/*
+===============
+areaZapFire
+===============
+*/
+void areaZapFire( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t end;
+ gentity_t *traceEnt;
+ vec3_t mins, maxs;
+
+ VectorSet( mins, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH );
+ VectorSet( maxs, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH );
+
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+
+ VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end );
+
+ G_UnlaggedOn( ent, muzzle, LEVEL2_AREAZAP_RANGE );
+ trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
+ G_UnlaggedOff( );
+
+ if( tr.surfaceFlags & SURF_NOIMPACT )
+ return;
+
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
+ ( traceEnt->s.eType == ET_BUILDABLE &&
+ BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 )
+ {
+ G_CreateNewZap( ent, traceEnt );
+ }
+}
+
+
+/*
+======================================================================
+
+LEVEL3
+
+======================================================================
+*/
+
+/*
+===============
+CheckPounceAttack
+===============
+*/
+qboolean CheckPounceAttack( gentity_t *ent )
+{
+ trace_t tr;
+ gentity_t *tent;
+ gentity_t *traceEnt;
+ int damage;
+
+ if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
+ {
+ ent->client->allowedToPounce = qfalse;
+ ent->client->pmext.pouncePayload = 0;
+ }
+
+ if( !ent->client->allowedToPounce )
+ return qfalse;
+
+ if( ent->client->ps.weaponTime )
+ return qfalse;
+
+ G_WideTrace( &tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH, &traceEnt );
+
+ if( traceEnt == NULL )
+ return qfalse;
+
+ // send blood impact
+ if( traceEnt->takedamage && traceEnt->client )
+ {
+ tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = traceEnt->s.number;
+ tent->s.eventParm = DirToByte( tr.plane.normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+
+ if( !traceEnt->takedamage )
+ return qfalse;
+
+ damage = (int)( ( (float)ent->client->pmext.pouncePayload
+ / (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG );
+
+ ent->client->pmext.pouncePayload = 0;
+
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
+ DAMAGE_NO_KNOCKBACK|DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE );
+
+ ent->client->allowedToPounce = qfalse;
+
+ return qtrue;
+}
+
+void bounceBallFire( gentity_t *ent )
+{
+ gentity_t *m;
+
+ m = fire_bounceBall( ent, muzzle, forward );
+
+// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
+}
+
+
+/*
+======================================================================
+
+LEVEL4
+
+======================================================================
+*/
+
+/*
+===============
+ChargeAttack
+===============
+*/
+void ChargeAttack( gentity_t *ent, gentity_t *victim )
+{
+ gentity_t *tent;
+ int damage;
+ vec3_t forward, normal;
+
+ if( level.time < victim->chargeRepeat )
+ return;
+
+ victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT;
+
+ VectorSubtract( victim->s.origin, ent->s.origin, forward );
+ VectorNormalize( forward );
+ VectorNegate( forward, normal );
+
+ if( victim->client )
+ {
+ tent = G_TempEntity( victim->s.origin, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = victim->s.number;
+ tent->s.eventParm = DirToByte( normal );
+ tent->s.weapon = ent->s.weapon;
+ tent->s.generic1 = ent->s.generic1; //weaponMode
+ }
+
+ if( !victim->takedamage )
+ return;
+
+ damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG );
+
+ G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE );
+}
+
+//======================================================================
+
+/*
+===============
+CalcMuzzlePoint
+
+set muzzle location relative to pivoting eye
+===============
+*/
+void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint )
+{
+ VectorCopy( ent->s.pos.trBase, muzzlePoint );
+ muzzlePoint[ 2 ] += ent->client->ps.viewheight;
+ VectorMA( muzzlePoint, 1, forward, muzzlePoint );
+ VectorMA( muzzlePoint, 1, right, muzzlePoint );
+ // snap to integer coordinates for more efficient network bandwidth usage
+ SnapVector( muzzlePoint );
+}
+
+/*
+===============
+FireWeapon3
+===============
+*/
+void FireWeapon3( gentity_t *ent )
+{
+ if( ent->client )
+ {
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+ }
+ else
+ {
+ AngleVectors( ent->s.angles2, forward, right, up );
+ VectorCopy( ent->s.pos.trBase, muzzle );
+ }
+
+ // fire the specific weapon
+ switch( ent->s.weapon )
+ {
+ case WP_ALEVEL3_UPG:
+ bounceBallFire( ent );
+ break;
+
+ case WP_ABUILD2:
+ slowBlobFire( ent );
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*
+===============
+FireWeapon2
+===============
+*/
+void FireWeapon2( gentity_t *ent )
+{
+ if( ent->client )
+ {
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+ }
+ else
+ {
+ AngleVectors( ent->s.angles2, forward, right, up );
+ VectorCopy( ent->s.pos.trBase, muzzle );
+ }
+
+ // fire the specific weapon
+ switch( ent->s.weapon )
+ {
+ case WP_ALEVEL1_UPG:
+ poisonCloud( ent );
+ break;
+ case WP_ALEVEL2_UPG:
+ areaZapFire( ent );
+ break;
+
+ case WP_LUCIFER_CANNON:
+ LCChargeFire( ent, qtrue );
+ break;
+
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ cancelBuildFire( ent );
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+===============
+FireWeapon
+===============
+*/
+void FireWeapon( gentity_t *ent )
+{
+ if( level.paused ) return;
+
+ if( ent->client )
+ {
+ // set aiming directions
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+ }
+ else
+ {
+ AngleVectors( ent->turretAim, forward, right, up );
+ VectorCopy( ent->s.pos.trBase, muzzle );
+ }
+
+ // fire the specific weapon
+ switch( ent->s.weapon )
+ {
+ case WP_ALEVEL1:
+ case WP_ALEVEL1_UPG:
+ meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
+ break;
+ case WP_ALEVEL3:
+ case WP_ALEVEL3_UPG:
+ meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
+ break;
+ case WP_ALEVEL2:
+ meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
+ break;
+ case WP_ALEVEL2_UPG:
+ meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
+ break;
+ case WP_ALEVEL4:
+ meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW );
+ break;
+
+ case WP_BLASTER:
+ blasterFire( ent );
+ break;
+ case WP_MACHINEGUN:
+ bulletFire( ent, RIFLE_SPREAD, RIFLE_DMG, MOD_MACHINEGUN );
+ break;
+ case WP_SHOTGUN:
+ shotgunFire( ent );
+ break;
+ case WP_CHAINGUN:
+ bulletFire( ent, CHAINGUN_SPREAD, CHAINGUN_DMG, MOD_CHAINGUN );
+ break;
+ case WP_FLAMER:
+ flamerFire( ent );
+ break;
+ case WP_PULSE_RIFLE:
+ pulseRifleFire( ent );
+ break;
+ case WP_MASS_DRIVER:
+ massDriverFire( ent );
+ break;
+ case WP_LUCIFER_CANNON:
+ LCChargeFire( ent, qfalse );
+ break;
+ case WP_LAS_GUN:
+ lasGunFire( ent );
+ break;
+ case WP_PAIN_SAW:
+ painSawFire( ent );
+ break;
+ case WP_GRENADE:
+ throwGrenade( ent );
+ break;
+
+ case WP_LOCKBLOB_LAUNCHER:
+ lockBlobLauncherFire( ent );
+ break;
+ case WP_HIVE:
+ hiveFire( ent );
+ break;
+ case WP_TESLAGEN:
+ teslaFire( ent );
+ break;
+ case WP_MGTURRET:
+ bulletFire( ent, MGTURRET_SPREAD, MGTURRET_DMG, MOD_MGTURRET );
+ break;
+
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ buildFire( ent, MN_A_BUILD );
+ break;
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ buildFire( ent, MN_H_BUILD );
+ break;
+ default:
+ break;
+ }
+}
+
diff --git a/src/game/tremulous.h b/src/game/tremulous.h
new file mode 100644
index 0000000..b8bb203
--- /dev/null
+++ b/src/game/tremulous.h
@@ -0,0 +1,626 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+/*
+ * ALIEN weapons
+ *
+ * _REPEAT - time in msec until the weapon can be used again
+ * _DMG - amount of damage the weapon does
+ *
+ * ALIEN_WDMG_MODIFIER - overall damage modifier for coarse tuning
+ *
+ */
+
+#define ALIEN_WDMG_MODIFIER 1.0f
+#define ADM(d) ((int)((float)d*ALIEN_WDMG_MODIFIER))
+
+#define ABUILDER_BUILD_REPEAT 500
+#define ABUILDER_CLAW_DMG ADM(20)
+#define ABUILDER_CLAW_RANGE 64.0f
+#define ABUILDER_CLAW_WIDTH 4.0f
+#define ABUILDER_CLAW_REPEAT 1000
+#define ABUILDER_CLAW_K_SCALE 1.0f
+#define ABUILDER_BASE_DELAY 17000
+#define ABUILDER_ADV_DELAY 12000
+#define ABUILDER_BLOB_DMG ADM(4)
+#define ABUILDER_BLOB_REPEAT 1000
+#define ABUILDER_BLOB_SPEED 800.0f
+#define ABUILDER_BLOB_SPEED_MOD 0.5f
+#define ABUILDER_BLOB_TIME 5000
+
+#define LEVEL0_BITE_DMG ADM(48)
+#define LEVEL0_BITE_RANGE 64.0f
+#define LEVEL0_BITE_WIDTH 6.0f
+#define LEVEL0_BITE_REPEAT 500
+#define LEVEL0_BITE_K_SCALE 1.0f
+
+#define LEVEL1_CLAW_DMG ADM(32)
+#define LEVEL1_CLAW_RANGE 96.0f
+#define LEVEL1_CLAW_WIDTH 10.0f
+#define LEVEL1_CLAW_REPEAT 600
+#define LEVEL1_CLAW_U_REPEAT 500
+#define LEVEL1_CLAW_K_SCALE 1.0f
+#define LEVEL1_CLAW_U_K_SCALE 1.0f
+#define LEVEL1_GRAB_RANGE 64.0f
+#define LEVEL1_GRAB_TIME 300
+#define LEVEL1_GRAB_U_TIME 450
+#define LEVEL1_PCLOUD_DMG ADM(4)
+#define LEVEL1_PCLOUD_RANGE 200.0f
+#define LEVEL1_PCLOUD_REPEAT 2000
+#define LEVEL1_PCLOUD_TIME 10000
+
+#define LEVEL2_CLAW_DMG ADM(40)
+#define LEVEL2_CLAW_RANGE 96.0f
+#define LEVEL2_CLAW_WIDTH 12.0f
+#define LEVEL2_CLAW_REPEAT 500
+#define LEVEL2_CLAW_K_SCALE 1.0f
+#define LEVEL2_CLAW_U_REPEAT 400
+#define LEVEL2_CLAW_U_K_SCALE 1.0f
+#define LEVEL2_AREAZAP_DMG ADM(80)
+#define LEVEL2_AREAZAP_RANGE 200.0f
+#define LEVEL2_AREAZAP_WIDTH 15.0f
+#define LEVEL2_AREAZAP_REPEAT 1500
+#define LEVEL2_AREAZAP_TIME 1000
+#define LEVEL2_AREAZAP_MAX_TARGETS 3
+#define LEVEL2_WALLJUMP_MAXSPEED 1000.0f
+
+#define LEVEL3_CLAW_DMG ADM(80)
+#define LEVEL3_CLAW_RANGE 96.0f
+#define LEVEL3_CLAW_WIDTH 16.0f
+#define LEVEL3_CLAW_REPEAT 700
+#define LEVEL3_CLAW_K_SCALE 1.0f
+#define LEVEL3_CLAW_U_REPEAT 600
+#define LEVEL3_CLAW_U_K_SCALE 1.0f
+#define LEVEL3_POUNCE_DMG ADM(100)
+#define LEVEL3_POUNCE_RANGE 72.0f
+#define LEVEL3_POUNCE_WIDTH 16.0f
+#define LEVEL3_POUNCE_SPEED 700
+#define LEVEL3_POUNCE_UPG_SPEED 800
+#define LEVEL3_POUNCE_SPEED_MOD 0.75f
+#define LEVEL3_POUNCE_CHARGE_TIME 700
+#define LEVEL3_POUNCE_TIME 400
+#define LEVEL3_BOUNCEBALL_DMG ADM(110)
+#define LEVEL3_BOUNCEBALL_REPEAT 1000
+#define LEVEL3_BOUNCEBALL_SPEED 1000.0f
+
+#define LEVEL4_CLAW_DMG ADM(100)
+#define LEVEL4_CLAW_RANGE 128.0f
+#define LEVEL4_CLAW_WIDTH 20.0f
+#define LEVEL4_CLAW_REPEAT 750
+#define LEVEL4_CLAW_K_SCALE 1.0f
+#define LEVEL4_REGEN_RANGE 200.0f
+#define LEVEL4_REGEN_MOD 2.0f
+#define LEVEL4_CHARGE_SPEED 2.0f
+#define LEVEL4_CHARGE_TIME 3000
+#define LEVEL4_CHARGE_CHARGE_TIME 1500
+#define LEVEL4_MIN_CHARGE_TIME 750
+#define LEVEL4_CHARGE_CHARGE_RATIO (LEVEL4_CHARGE_TIME/LEVEL4_CHARGE_CHARGE_TIME)
+#define LEVEL4_CHARGE_REPEAT 1000
+#define LEVEL4_CHARGE_DMG ADM(110)
+
+
+
+/*
+ * ALIEN classes
+ *
+ * _SPEED - fraction of Q3A run speed the class can move
+ * _REGEN - health per second regained
+ *
+ * ALIEN_HLTH_MODIFIER - overall health modifier for coarse tuning
+ *
+ */
+
+#define ALIEN_HLTH_MODIFIER 1.0f
+#define AHM(h) ((int)((float)h*ALIEN_HLTH_MODIFIER))
+
+#define ALIEN_VALUE_MODIFIER 1.0f
+#define AVM(h) ((int)((float)h*ALIEN_VALUE_MODIFIER))
+
+#define ABUILDER_SPEED 0.8f
+#define ABUILDER_VALUE AVM(200)
+#define ABUILDER_HEALTH AHM(50)
+#define ABUILDER_REGEN 2
+#define ABUILDER_COST 0
+
+#define ABUILDER_UPG_SPEED 1.0f
+#define ABUILDER_UPG_VALUE AVM(250)
+#define ABUILDER_UPG_HEALTH AHM(75)
+#define ABUILDER_UPG_REGEN 3
+#define ABUILDER_UPG_COST 0
+
+#define LEVEL0_SPEED 1.3f
+#define LEVEL0_VALUE AVM(175)
+#define LEVEL0_HEALTH AHM(25)
+#define LEVEL0_REGEN 1
+#define LEVEL0_COST 0
+
+#define LEVEL1_SPEED 1.25f
+#define LEVEL1_VALUE AVM(225)
+#define LEVEL1_HEALTH AHM(75)
+#define LEVEL1_REGEN 2
+#define LEVEL1_COST 1
+
+#define LEVEL1_UPG_SPEED 1.25f
+#define LEVEL1_UPG_VALUE AVM(275)
+#define LEVEL1_UPG_HEALTH AHM(100)
+#define LEVEL1_UPG_REGEN 3
+#define LEVEL1_UPG_COST 1
+
+#define LEVEL2_SPEED 1.2f
+#define LEVEL2_VALUE AVM(350)
+#define LEVEL2_HEALTH AHM(150)
+#define LEVEL2_REGEN 4
+#define LEVEL2_COST 1
+
+#define LEVEL2_UPG_SPEED 1.2f
+#define LEVEL2_UPG_VALUE AVM(450)
+#define LEVEL2_UPG_HEALTH AHM(175)
+#define LEVEL2_UPG_REGEN 5
+#define LEVEL2_UPG_COST 1
+
+#define LEVEL3_SPEED 1.1f
+#define LEVEL3_VALUE AVM(500)
+#define LEVEL3_HEALTH AHM(200)
+#define LEVEL3_REGEN 6
+#define LEVEL3_COST 1
+
+#define LEVEL3_UPG_SPEED 1.1f
+#define LEVEL3_UPG_VALUE AVM(600)
+#define LEVEL3_UPG_HEALTH AHM(250)
+#define LEVEL3_UPG_REGEN 7
+#define LEVEL3_UPG_COST 1
+
+#define LEVEL4_SPEED 1.2f
+#define LEVEL4_VALUE AVM(800)
+#define LEVEL4_HEALTH AHM(400)
+#define LEVEL4_REGEN 7
+#define LEVEL4_COST 2
+
+
+
+/*
+ * ALIEN buildables
+ *
+ * _BP - build points required for this buildable
+ * _BT - build time required for this buildable
+ * _REGEN - the amount of health per second regained
+ * _SPLASHDAMGE - the amount of damage caused by this buildable when melting
+ * _SPLASHRADIUS - the radius around which it does this damage
+ *
+ * CREEP_BASESIZE - the maximum distance a buildable can be from an egg/overmind
+ * ALIEN_BHLTH_MODIFIER - overall health modifier for coarse tuning
+ *
+ */
+
+#define ALIEN_BHLTH_MODIFIER 1.0f
+#define ABHM(h) ((int)((float)h*ALIEN_BHLTH_MODIFIER))
+
+#define CREEP_BASESIZE 700
+#define CREEP_TIMEOUT 1000
+#define CREEP_MODIFIER 0.5f
+#define CREEP_ARMOUR_MODIFIER 0.75f
+#define CREEP_SCALEDOWN_TIME 3000
+
+#define ASPAWN_BP 10
+#define ASPAWN_BT 15000
+#define ASPAWN_HEALTH ABHM(250)
+#define ASPAWN_REGEN 8
+#define ASPAWN_SPLASHDAMAGE 50
+#define ASPAWN_SPLASHRADIUS 50
+#define ASPAWN_CREEPSIZE 120
+#define ASPAWN_VALUE 150
+
+#define BARRICADE_BP 10
+#define BARRICADE_BT 20000
+#define BARRICADE_HEALTH ABHM(200)
+#define BARRICADE_REGEN 14
+#define BARRICADE_SPLASHDAMAGE 50
+#define BARRICADE_SPLASHRADIUS 50
+#define BARRICADE_CREEPSIZE 120
+
+#define BOOSTER_BP 12
+#define BOOSTER_BT 15000
+#define BOOSTER_HEALTH ABHM(150)
+#define BOOSTER_REGEN 8
+#define BOOSTER_SPLASHDAMAGE 50
+#define BOOSTER_SPLASHRADIUS 50
+#define BOOSTER_CREEPSIZE 120
+#define BOOSTER_INTERVAL 30000 //time in msec between uses (per player)
+#define BOOSTER_REGEN_MOD 2.0f
+#define BOOST_TIME 30000
+
+#define ACIDTUBE_BP 8
+#define ACIDTUBE_BT 15000
+#define ACIDTUBE_HEALTH ABHM(125)
+#define ACIDTUBE_REGEN 10
+#define ACIDTUBE_SPLASHDAMAGE 6
+#define ACIDTUBE_SPLASHRADIUS 300
+#define ACIDTUBE_CREEPSIZE 120
+#define ACIDTUBE_RANGE 300.0f
+#define ACIDTUBE_REPEAT 3000
+#define ACIDTUBE_K_SCALE 1.0f
+
+#define HIVE_BP 12
+#define HIVE_BT 20000
+#define HIVE_HEALTH ABHM(125)
+#define HIVE_REGEN 10
+#define HIVE_SPLASHDAMAGE 30
+#define HIVE_SPLASHRADIUS 200
+#define HIVE_CREEPSIZE 120
+#define HIVE_RANGE 400.0f
+#define HIVE_REPEAT 5000
+#define HIVE_K_SCALE 1.0f
+#define HIVE_DMG 50
+#define HIVE_SPEED 240.0f
+#define HIVE_DIR_CHANGE_PERIOD 500
+
+#define TRAPPER_BP 8
+#define TRAPPER_BT 12000
+#define TRAPPER_HEALTH ABHM(50)
+#define TRAPPER_REGEN 6
+#define TRAPPER_SPLASHDAMAGE 15
+#define TRAPPER_SPLASHRADIUS 100
+#define TRAPPER_CREEPSIZE 30
+#define TRAPPER_RANGE 400
+#define TRAPPER_REPEAT 1000
+#define TRAPPER_K_SCALE 1.0f
+#define LOCKBLOB_SPEED 650.0f
+#define LOCKBLOB_LOCKTIME 5000
+#define LOCKBLOB_DOT 0.85f // max angle = acos( LOCKBLOB_DOT )
+#define LOCKBLOB_K_SCALE 1.0f
+
+#define OVERMIND_BP 0
+#define OVERMIND_BT 30000
+#define OVERMIND_HEALTH ABHM(750)
+#define OVERMIND_REGEN 6
+#define OVERMIND_SPLASHDAMAGE 15
+#define OVERMIND_SPLASHRADIUS 300
+#define OVERMIND_CREEPSIZE 120
+#define OVERMIND_ATTACK_RANGE 150.0f
+#define OVERMIND_ATTACK_REPEAT 1000
+#define OVERMIND_VALUE 300
+
+#define HOVEL_BP 0
+#define HOVEL_BT 15000
+#define HOVEL_HEALTH ABHM(375)
+#define HOVEL_REGEN 20
+#define HOVEL_SPLASHDAMAGE 20
+#define HOVEL_SPLASHRADIUS 200
+#define HOVEL_CREEPSIZE 120
+
+
+
+/*
+ * ALIEN misc
+ *
+ * ALIENSENSE_RANGE - the distance alien sense is useful for
+ *
+ */
+
+#define ALIENSENSE_RANGE 1000.0f
+
+#define ALIEN_POISON_TIME 5000
+#define ALIEN_POISON_DMG 5
+#define ALIEN_POISON_DIVIDER (1.0f/1.32f) //about 1.0/(time`th root of damage)
+
+#define ALIEN_SPAWN_REPEAT_TIME 10000
+
+#define ALIEN_REGEN_DAMAGE_TIME 2000 //msec since damage that regen starts again
+
+/*
+ * HUMAN weapons
+ *
+ * _REPEAT - time between firings
+ * _RELOAD - time needed to reload
+ * _PRICE - amount in credits weapon costs
+ *
+ * HUMAN_WDMG_MODIFIER - overall damage modifier for coarse tuning
+ *
+ */
+
+#define HUMAN_WDMG_MODIFIER 1.0f
+#define HDM(d) ((int)((float)d*HUMAN_WDMG_MODIFIER))
+
+#define BLASTER_REPEAT 600
+#define BLASTER_K_SCALE 1.0f
+#define BLASTER_SPREAD 200
+#define BLASTER_SPEED 1400
+#define BLASTER_DMG HDM(9)
+
+#define RIFLE_CLIPSIZE 30
+#define RIFLE_MAXCLIPS 6
+#define RIFLE_REPEAT 90
+#define RIFLE_K_SCALE 1.0f
+#define RIFLE_RELOAD 2000
+#define RIFLE_PRICE 0
+#define RIFLE_SPREAD 200
+#define RIFLE_DMG HDM(5)
+
+#define PAINSAW_PRICE 100
+#define PAINSAW_REPEAT 75
+#define PAINSAW_K_SCALE 1.0f
+#define PAINSAW_DAMAGE HDM(15)
+#define PAINSAW_RANGE 40.0f
+
+#define GRENADE_PRICE 200
+#define GRENADE_REPEAT 0
+#define GRENADE_K_SCALE 1.0f
+#define GRENADE_DAMAGE HDM(310)
+#define GRENADE_RANGE 192.0f
+#define GRENADE_SPEED 400.0f
+
+#define SHOTGUN_PRICE 150
+#define SHOTGUN_SHELLS 8
+#define SHOTGUN_PELLETS 8 //used to sync server and client side
+#define SHOTGUN_MAXCLIPS 3
+#define SHOTGUN_REPEAT 1000
+#define SHOTGUN_K_SCALE 1.0f
+#define SHOTGUN_RELOAD 2000
+#define SHOTGUN_SPREAD 900
+#define SHOTGUN_DMG HDM(7)
+
+#define LASGUN_PRICE 250
+#define LASGUN_AMMO 200
+#define LASGUN_REPEAT 200
+#define LASGUN_K_SCALE 1.0f
+#define LASGUN_RELOAD 2000
+#define LASGUN_DAMAGE HDM(9)
+
+#define MDRIVER_PRICE 350
+#define MDRIVER_CLIPSIZE 5
+#define MDRIVER_MAXCLIPS 4
+#define MDRIVER_DMG HDM(38)
+#define MDRIVER_REPEAT 1000
+#define MDRIVER_K_SCALE 1.0f
+#define MDRIVER_RELOAD 2000
+
+#define CHAINGUN_PRICE 400
+#define CHAINGUN_BULLETS 300
+#define CHAINGUN_REPEAT 80
+#define CHAINGUN_K_SCALE 1.0f
+#define CHAINGUN_SPREAD 1000
+#define CHAINGUN_DMG HDM(6)
+
+#define PRIFLE_PRICE 400
+#define PRIFLE_CLIPS 50
+#define PRIFLE_MAXCLIPS 4
+#define PRIFLE_REPEAT 100
+#define PRIFLE_K_SCALE 1.0f
+#define PRIFLE_RELOAD 2000
+#define PRIFLE_DMG HDM(9)
+#define PRIFLE_SPEED 1000
+
+#define FLAMER_PRICE 450
+#define FLAMER_GAS 150
+#define FLAMER_REPEAT 200
+#define FLAMER_K_SCALE 1.0f
+#define FLAMER_DMG HDM(20)
+#define FLAMER_RADIUS 50
+#define FLAMER_LIFETIME 800.0f
+#define FLAMER_SPEED 200.0f
+#define FLAMER_LAG 0.65f //the amount of player velocity that is added to the fireball
+
+#define LCANNON_PRICE 600
+#define LCANNON_AMMO 90
+#define LCANNON_REPEAT 500
+#define LCANNON_K_SCALE 1.0f
+#define LCANNON_CHARGEREPEAT 1000
+#define LCANNON_RELOAD 2000
+#define LCANNON_DAMAGE HDM(265)
+#define LCANNON_RADIUS 150
+#define LCANNON_SECONDARY_DAMAGE HDM(27)
+#define LCANNON_SECONDARY_RADIUS 75
+#define LCANNON_SPEED 350
+#define LCANNON_CHARGE_TIME 2000
+#define LCANNON_TOTAL_CHARGE 255
+#define LCANNON_MIN_CHARGE 50
+
+#define HBUILD_PRICE 0
+#define HBUILD_REPEAT 1000
+#define HBUILD_DELAY 17500
+#define HBUILD_HEALRATE 18
+
+#define HBUILD2_PRICE 0
+#define HBUILD2_REPEAT 1000
+#define HBUILD2_DELAY 15000
+
+
+
+/*
+ * HUMAN upgrades
+ */
+
+#define LIGHTARMOUR_PRICE 70
+#define LIGHTARMOUR_POISON_PROTECTION 1
+
+#define HELMET_PRICE 90
+#define HELMET_RANGE 1000.0f
+#define HELMET_POISON_PROTECTION 2
+
+#define MEDKIT_PRICE 0
+
+#define BATTPACK_PRICE 100
+#define BATTPACK_MODIFIER 1.5f //modifier for extra energy storage available
+
+#define JETPACK_PRICE 120
+#define JETPACK_FLOAT_SPEED 128.0f //up movement speed
+#define JETPACK_SINK_SPEED 192.0f //down movement speed
+#define JETPACK_DISABLE_TIME 1000 //time to disable the jetpack when player damaged
+#define JETPACK_DISABLE_CHANCE 0.3f
+
+#define BSUIT_PRICE 400
+#define BSUIT_POISON_PROTECTION 4
+
+#define MGCLIP_PRICE 0
+
+#define CGAMMO_PRICE 0
+
+#define GAS_PRICE 0
+
+#define MEDKIT_POISON_IMMUNITY_TIME 0
+#define MEDKIT_STARTUP_TIME 4000
+#define MEDKIT_STARTUP_SPEED 5
+
+
+/*
+ * HUMAN buildables
+ *
+ * _BP - build points required for this buildable
+ * _BT - build time required for this buildable
+ * _SPLASHDAMGE - the amount of damage caused by this buildable when it blows up
+ * _SPLASHRADIUS - the radius around which it does this damage
+ *
+ * REACTOR_BASESIZE - the maximum distance a buildable can be from an reactor
+ * REPEATER_BASESIZE - the maximum distance a buildable can be from a repeater
+ * HUMAN_BHLTH_MODIFIER - overall health modifier for coarse tuning
+ *
+ */
+
+#define HUMAN_BHLTH_MODIFIER 1.0f
+#define HBHM(h) ((int)((float)h*HUMAN_BHLTH_MODIFIER))
+
+#define REACTOR_BASESIZE 1000
+#define REPEATER_BASESIZE 500
+#define HUMAN_DETONATION_DELAY 5000
+
+#define HSPAWN_BP 10
+#define HSPAWN_BT 10000
+#define HSPAWN_HEALTH HBHM(310)
+#define HSPAWN_SPLASHDAMAGE 50
+#define HSPAWN_SPLASHRADIUS 100
+#define HSPAWN_VALUE 1
+
+#define MEDISTAT_BP 8
+#define MEDISTAT_BT 10000
+#define MEDISTAT_HEALTH HBHM(190)
+#define MEDISTAT_SPLASHDAMAGE 50
+#define MEDISTAT_SPLASHRADIUS 100
+
+#define MGTURRET_BP 8
+#define MGTURRET_BT 10000
+#define MGTURRET_HEALTH HBHM(190)
+#define MGTURRET_SPLASHDAMAGE 100
+#define MGTURRET_SPLASHRADIUS 100
+#define MGTURRET_ANGULARSPEED 8 //degrees/think ~= 200deg/sec
+#define MGTURRET_ACCURACYTOLERANCE MGTURRET_ANGULARSPEED / 1.5f //angular difference for turret to fire
+#define MGTURRET_VERTICALCAP 30 // +/- maximum pitch
+#define MGTURRET_REPEAT 100
+#define MGTURRET_K_SCALE 1.0f
+#define MGTURRET_RANGE 300.0f
+#define MGTURRET_SPREAD 200
+#define MGTURRET_DMG HDM(4)
+#define MGTURRET_DCC_ANGULARSPEED 10
+#define MGTURRET_DCC_ACCURACYTOLERANCE MGTURRET_DCC_ANGULARSPEED / 1.5f
+#define MGTURRET_GRAB_ANGULARSPEED 3
+#define MGTURRET_GRAB_ACCURACYTOLERANCE MGTURRET_GRAB_ANGULARSPEED / 1.5f
+
+#define TESLAGEN_BP 10
+#define TESLAGEN_BT 15000
+#define TESLAGEN_HEALTH HBHM(220)
+#define TESLAGEN_SPLASHDAMAGE 50
+#define TESLAGEN_SPLASHRADIUS 100
+#define TESLAGEN_REPEAT 250
+#define TESLAGEN_K_SCALE 4.0f
+#define TESLAGEN_RANGE 250
+#define TESLAGEN_DMG HDM(9)
+
+#define DC_BP 8
+#define DC_BT 10000
+#define DC_HEALTH HBHM(190)
+#define DC_SPLASHDAMAGE 50
+#define DC_SPLASHRADIUS 100
+
+#define ARMOURY_BP 10
+#define ARMOURY_BT 10000
+#define ARMOURY_HEALTH HBHM(280)
+#define ARMOURY_SPLASHDAMAGE 50
+#define ARMOURY_SPLASHRADIUS 100
+
+#define REACTOR_BP 0
+#define REACTOR_BT 20000
+#define REACTOR_HEALTH HBHM(930)
+#define REACTOR_SPLASHDAMAGE 200
+#define REACTOR_SPLASHRADIUS 300
+#define REACTOR_ATTACK_RANGE 100.0f
+#define REACTOR_ATTACK_REPEAT 1000
+#define REACTOR_ATTACK_DAMAGE 40
+#define REACTOR_VALUE 2
+
+#define REPEATER_BP 0
+#define REPEATER_BT 10000
+#define REPEATER_HEALTH HBHM(250)
+#define REPEATER_SPLASHDAMAGE 50
+#define REPEATER_SPLASHRADIUS 100
+#define REPEATER_INACTIVE_TIME 90000
+
+/*
+ * HUMAN misc
+ */
+
+#define HUMAN_SPRINT_MODIFIER 1.2f
+#define HUMAN_JOG_MODIFIER 1.0f
+#define HUMAN_BACK_MODIFIER 0.8f
+#define HUMAN_SIDE_MODIFIER 0.9f
+
+#define STAMINA_STOP_RESTORE 25
+#define STAMINA_WALK_RESTORE 15
+#define STAMINA_SPRINT_TAKE 8
+#define STAMINA_LARMOUR_TAKE 4
+
+#define HUMAN_SPAWN_REPEAT_TIME 10000
+
+/*
+ * Misc
+ */
+
+#define MIN_FALL_DISTANCE 30.0f //the fall distance at which fall damage kicks in
+#define MAX_FALL_DISTANCE 120.0f //the fall distance at which maximum damage is dealt
+#define AVG_FALL_DISTANCE ((MIN_FALL_DISTANCE+MAX_FALL_DISTANCE)/2.0f)
+
+#define HUMAN_MAXED 900 //a human with a strong selection of weapons/upgrades
+#define HUMAN_MAX_CREDITS 2000
+#define ALIEN_MAX_KILLS 9
+#define ALIEN_MAX_SINGLE_KILLS 3
+
+#define FREEKILL_PERIOD 120000 //msec
+#define FREEKILL_ALIEN 1
+#define FREEKILL_HUMAN LEVEL0_VALUE
+
+#define DEFAULT_ALIEN_BUILDPOINTS "130"
+#define DEFAULT_ALIEN_STAGE2_THRESH "20"
+#define DEFAULT_ALIEN_STAGE3_THRESH "40"
+#define DEFAULT_ALIEN_MAX_STAGE "2"
+#define DEFAULT_HUMAN_BUILDPOINTS "130"
+#define DEFAULT_HUMAN_STAGE2_THRESH "20"
+#define DEFAULT_HUMAN_STAGE3_THRESH "40"
+#define DEFAULT_HUMAN_MAX_STAGE "2"
+
+#define DAMAGE_FRACTION_FOR_KILL 0.5f //how much damage players (versus structures) need to
+ //do to increment the stage kill counters
+
+// g_suddenDeathMode settings
+#define SDMODE_BP 0
+#define SDMODE_NO_BUILD 1
+#define SDMODE_SELECTIVE 2
+#define SDMODE_NO_DECON 3
diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c
new file mode 100644
index 0000000..196d2f5
--- /dev/null
+++ b/src/qcommon/q_math.c
@@ -0,0 +1,1562 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+// q_math.c -- stateless support routines that are included in each code module
+
+// Some of the vector functions are static inline in q_shared.h. q3asm
+// doesn't understand static functions though, so we only want them in
+// one file. That's what this is about.
+#ifdef Q3_VM
+#define __Q3_VM_MATH
+#endif
+
+#include "q_shared.h"
+
+vec3_t vec3_origin = {0,0,0};
+vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
+
+
+vec4_t colorBlack = {0, 0, 0, 1};
+vec4_t colorRed = {1, 0, 0, 1};
+vec4_t colorGreen = {0, 1, 0, 1};
+vec4_t colorBlue = {0, 0, 1, 1};
+vec4_t colorYellow = {1, 1, 0, 1};
+vec4_t colorMagenta= {1, 0, 1, 1};
+vec4_t colorCyan = {0, 1, 1, 1};
+vec4_t colorWhite = {1, 1, 1, 1};
+vec4_t colorLtGrey = {0.75, 0.75, 0.75, 1};
+vec4_t colorMdGrey = {0.5, 0.5, 0.5, 1};
+vec4_t colorDkGrey = {0.25, 0.25, 0.25, 1};
+
+vec4_t g_color_table[8] =
+ {
+ {0.0, 0.0, 0.0, 1.0},
+ {1.0, 0.0, 0.0, 1.0},
+ {0.0, 1.0, 0.0, 1.0},
+ {1.0, 1.0, 0.0, 1.0},
+ {0.0, 0.0, 1.0, 1.0},
+ {0.0, 1.0, 1.0, 1.0},
+ {1.0, 0.0, 1.0, 1.0},
+ {1.0, 1.0, 1.0, 1.0},
+ };
+
+
+vec3_t bytedirs[NUMVERTEXNORMALS] =
+{
+{-0.525731f, 0.000000f, 0.850651f}, {-0.442863f, 0.238856f, 0.864188f},
+{-0.295242f, 0.000000f, 0.955423f}, {-0.309017f, 0.500000f, 0.809017f},
+{-0.162460f, 0.262866f, 0.951056f}, {0.000000f, 0.000000f, 1.000000f},
+{0.000000f, 0.850651f, 0.525731f}, {-0.147621f, 0.716567f, 0.681718f},
+{0.147621f, 0.716567f, 0.681718f}, {0.000000f, 0.525731f, 0.850651f},
+{0.309017f, 0.500000f, 0.809017f}, {0.525731f, 0.000000f, 0.850651f},
+{0.295242f, 0.000000f, 0.955423f}, {0.442863f, 0.238856f, 0.864188f},
+{0.162460f, 0.262866f, 0.951056f}, {-0.681718f, 0.147621f, 0.716567f},
+{-0.809017f, 0.309017f, 0.500000f},{-0.587785f, 0.425325f, 0.688191f},
+{-0.850651f, 0.525731f, 0.000000f},{-0.864188f, 0.442863f, 0.238856f},
+{-0.716567f, 0.681718f, 0.147621f},{-0.688191f, 0.587785f, 0.425325f},
+{-0.500000f, 0.809017f, 0.309017f}, {-0.238856f, 0.864188f, 0.442863f},
+{-0.425325f, 0.688191f, 0.587785f}, {-0.716567f, 0.681718f, -0.147621f},
+{-0.500000f, 0.809017f, -0.309017f}, {-0.525731f, 0.850651f, 0.000000f},
+{0.000000f, 0.850651f, -0.525731f}, {-0.238856f, 0.864188f, -0.442863f},
+{0.000000f, 0.955423f, -0.295242f}, {-0.262866f, 0.951056f, -0.162460f},
+{0.000000f, 1.000000f, 0.000000f}, {0.000000f, 0.955423f, 0.295242f},
+{-0.262866f, 0.951056f, 0.162460f}, {0.238856f, 0.864188f, 0.442863f},
+{0.262866f, 0.951056f, 0.162460f}, {0.500000f, 0.809017f, 0.309017f},
+{0.238856f, 0.864188f, -0.442863f},{0.262866f, 0.951056f, -0.162460f},
+{0.500000f, 0.809017f, -0.309017f},{0.850651f, 0.525731f, 0.000000f},
+{0.716567f, 0.681718f, 0.147621f}, {0.716567f, 0.681718f, -0.147621f},
+{0.525731f, 0.850651f, 0.000000f}, {0.425325f, 0.688191f, 0.587785f},
+{0.864188f, 0.442863f, 0.238856f}, {0.688191f, 0.587785f, 0.425325f},
+{0.809017f, 0.309017f, 0.500000f}, {0.681718f, 0.147621f, 0.716567f},
+{0.587785f, 0.425325f, 0.688191f}, {0.955423f, 0.295242f, 0.000000f},
+{1.000000f, 0.000000f, 0.000000f}, {0.951056f, 0.162460f, 0.262866f},
+{0.850651f, -0.525731f, 0.000000f},{0.955423f, -0.295242f, 0.000000f},
+{0.864188f, -0.442863f, 0.238856f}, {0.951056f, -0.162460f, 0.262866f},
+{0.809017f, -0.309017f, 0.500000f}, {0.681718f, -0.147621f, 0.716567f},
+{0.850651f, 0.000000f, 0.525731f}, {0.864188f, 0.442863f, -0.238856f},
+{0.809017f, 0.309017f, -0.500000f}, {0.951056f, 0.162460f, -0.262866f},
+{0.525731f, 0.000000f, -0.850651f}, {0.681718f, 0.147621f, -0.716567f},
+{0.681718f, -0.147621f, -0.716567f},{0.850651f, 0.000000f, -0.525731f},
+{0.809017f, -0.309017f, -0.500000f}, {0.864188f, -0.442863f, -0.238856f},
+{0.951056f, -0.162460f, -0.262866f}, {0.147621f, 0.716567f, -0.681718f},
+{0.309017f, 0.500000f, -0.809017f}, {0.425325f, 0.688191f, -0.587785f},
+{0.442863f, 0.238856f, -0.864188f}, {0.587785f, 0.425325f, -0.688191f},
+{0.688191f, 0.587785f, -0.425325f}, {-0.147621f, 0.716567f, -0.681718f},
+{-0.309017f, 0.500000f, -0.809017f}, {0.000000f, 0.525731f, -0.850651f},
+{-0.525731f, 0.000000f, -0.850651f}, {-0.442863f, 0.238856f, -0.864188f},
+{-0.295242f, 0.000000f, -0.955423f}, {-0.162460f, 0.262866f, -0.951056f},
+{0.000000f, 0.000000f, -1.000000f}, {0.295242f, 0.000000f, -0.955423f},
+{0.162460f, 0.262866f, -0.951056f}, {-0.442863f, -0.238856f, -0.864188f},
+{-0.309017f, -0.500000f, -0.809017f}, {-0.162460f, -0.262866f, -0.951056f},
+{0.000000f, -0.850651f, -0.525731f}, {-0.147621f, -0.716567f, -0.681718f},
+{0.147621f, -0.716567f, -0.681718f}, {0.000000f, -0.525731f, -0.850651f},
+{0.309017f, -0.500000f, -0.809017f}, {0.442863f, -0.238856f, -0.864188f},
+{0.162460f, -0.262866f, -0.951056f}, {0.238856f, -0.864188f, -0.442863f},
+{0.500000f, -0.809017f, -0.309017f}, {0.425325f, -0.688191f, -0.587785f},
+{0.716567f, -0.681718f, -0.147621f}, {0.688191f, -0.587785f, -0.425325f},
+{0.587785f, -0.425325f, -0.688191f}, {0.000000f, -0.955423f, -0.295242f},
+{0.000000f, -1.000000f, 0.000000f}, {0.262866f, -0.951056f, -0.162460f},
+{0.000000f, -0.850651f, 0.525731f}, {0.000000f, -0.955423f, 0.295242f},
+{0.238856f, -0.864188f, 0.442863f}, {0.262866f, -0.951056f, 0.162460f},
+{0.500000f, -0.809017f, 0.309017f}, {0.716567f, -0.681718f, 0.147621f},
+{0.525731f, -0.850651f, 0.000000f}, {-0.238856f, -0.864188f, -0.442863f},
+{-0.500000f, -0.809017f, -0.309017f}, {-0.262866f, -0.951056f, -0.162460f},
+{-0.850651f, -0.525731f, 0.000000f}, {-0.716567f, -0.681718f, -0.147621f},
+{-0.716567f, -0.681718f, 0.147621f}, {-0.525731f, -0.850651f, 0.000000f},
+{-0.500000f, -0.809017f, 0.309017f}, {-0.238856f, -0.864188f, 0.442863f},
+{-0.262866f, -0.951056f, 0.162460f}, {-0.864188f, -0.442863f, 0.238856f},
+{-0.809017f, -0.309017f, 0.500000f}, {-0.688191f, -0.587785f, 0.425325f},
+{-0.681718f, -0.147621f, 0.716567f}, {-0.442863f, -0.238856f, 0.864188f},
+{-0.587785f, -0.425325f, 0.688191f}, {-0.309017f, -0.500000f, 0.809017f},
+{-0.147621f, -0.716567f, 0.681718f}, {-0.425325f, -0.688191f, 0.587785f},
+{-0.162460f, -0.262866f, 0.951056f}, {0.442863f, -0.238856f, 0.864188f},
+{0.162460f, -0.262866f, 0.951056f}, {0.309017f, -0.500000f, 0.809017f},
+{0.147621f, -0.716567f, 0.681718f}, {0.000000f, -0.525731f, 0.850651f},
+{0.425325f, -0.688191f, 0.587785f}, {0.587785f, -0.425325f, 0.688191f},
+{0.688191f, -0.587785f, 0.425325f}, {-0.955423f, 0.295242f, 0.000000f},
+{-0.951056f, 0.162460f, 0.262866f}, {-1.000000f, 0.000000f, 0.000000f},
+{-0.850651f, 0.000000f, 0.525731f}, {-0.955423f, -0.295242f, 0.000000f},
+{-0.951056f, -0.162460f, 0.262866f}, {-0.864188f, 0.442863f, -0.238856f},
+{-0.951056f, 0.162460f, -0.262866f}, {-0.809017f, 0.309017f, -0.500000f},
+{-0.864188f, -0.442863f, -0.238856f}, {-0.951056f, -0.162460f, -0.262866f},
+{-0.809017f, -0.309017f, -0.500000f}, {-0.681718f, 0.147621f, -0.716567f},
+{-0.681718f, -0.147621f, -0.716567f}, {-0.850651f, 0.000000f, -0.525731f},
+{-0.688191f, 0.587785f, -0.425325f}, {-0.587785f, 0.425325f, -0.688191f},
+{-0.425325f, 0.688191f, -0.587785f}, {-0.425325f, -0.688191f, -0.587785f},
+{-0.587785f, -0.425325f, -0.688191f}, {-0.688191f, -0.587785f, -0.425325f}
+};
+
+//==============================================================
+
+int Q_rand( int *seed ) {
+ *seed = (69069 * *seed + 1);
+ return *seed;
+}
+
+float Q_random( int *seed ) {
+ return ( Q_rand( seed ) & 0xffff ) / (float)0x10000;
+}
+
+float Q_crandom( int *seed ) {
+ return 2.0 * ( Q_random( seed ) - 0.5 );
+}
+
+//=======================================================
+
+signed char ClampChar( int i ) {
+ if ( i < -128 ) {
+ return -128;
+ }
+ if ( i > 127 ) {
+ return 127;
+ }
+ return i;
+}
+
+signed short ClampShort( int i ) {
+ if ( i < -32768 ) {
+ return -32768;
+ }
+ if ( i > 0x7fff ) {
+ return 0x7fff;
+ }
+ return i;
+}
+
+
+// this isn't a real cheap function to call!
+int DirToByte( vec3_t dir ) {
+ int i, best;
+ float d, bestd;
+
+ if ( !dir ) {
+ return 0;
+ }
+
+ bestd = 0;
+ best = 0;
+ for (i=0 ; i<NUMVERTEXNORMALS ; i++)
+ {
+ d = DotProduct (dir, bytedirs[i]);
+ if (d > bestd)
+ {
+ bestd = d;
+ best = i;
+ }
+ }
+
+ return best;
+}
+
+void ByteToDir( int b, vec3_t dir ) {
+ if ( b < 0 || b >= NUMVERTEXNORMALS ) {
+ VectorCopy( vec3_origin, dir );
+ return;
+ }
+ VectorCopy (bytedirs[b], dir);
+}
+
+
+unsigned ColorBytes3 (float r, float g, float b) {
+ unsigned i;
+
+ ( (byte *)&i )[0] = r * 255;
+ ( (byte *)&i )[1] = g * 255;
+ ( (byte *)&i )[2] = b * 255;
+
+ return i;
+}
+
+unsigned ColorBytes4 (float r, float g, float b, float a) {
+ unsigned i;
+
+ ( (byte *)&i )[0] = r * 255;
+ ( (byte *)&i )[1] = g * 255;
+ ( (byte *)&i )[2] = b * 255;
+ ( (byte *)&i )[3] = a * 255;
+
+ return i;
+}
+
+float NormalizeColor( const vec3_t in, vec3_t out ) {
+ float max;
+
+ max = in[0];
+ if ( in[1] > max ) {
+ max = in[1];
+ }
+ if ( in[2] > max ) {
+ max = in[2];
+ }
+
+ if ( !max ) {
+ VectorClear( out );
+ } else {
+ out[0] = in[0] / max;
+ out[1] = in[1] / max;
+ out[2] = in[2] / max;
+ }
+ return max;
+}
+
+
+/*
+=====================
+PlaneFromPoints
+
+Returns false if the triangle is degenrate.
+The normal will point out of the clock for clockwise ordered points
+=====================
+*/
+qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) {
+ vec3_t d1, d2;
+
+ VectorSubtract( b, a, d1 );
+ VectorSubtract( c, a, d2 );
+ CrossProduct( d2, d1, plane );
+ if ( VectorNormalize( plane ) == 0 ) {
+ return qfalse;
+ }
+
+ plane[3] = DotProduct( a, plane );
+ return qtrue;
+}
+
+/*
+===============
+RotatePointAroundVector
+
+This is not implemented very well...
+===============
+*/
+void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point,
+ float degrees ) {
+ float sin_a;
+ float cos_a;
+ float cos_ia;
+ float i_i_ia;
+ float j_j_ia;
+ float k_k_ia;
+ float i_j_ia;
+ float i_k_ia;
+ float j_k_ia;
+ float a_sin;
+ float b_sin;
+ float c_sin;
+ float rot[3][3];
+
+ cos_ia = DEG2RAD(degrees);
+ sin_a = sin(cos_ia);
+ cos_a = cos(cos_ia);
+ cos_ia = 1.0F - cos_a;
+
+ i_i_ia = dir[0] * dir[0] * cos_ia;
+ j_j_ia = dir[1] * dir[1] * cos_ia;
+ k_k_ia = dir[2] * dir[2] * cos_ia;
+ i_j_ia = dir[0] * dir[1] * cos_ia;
+ i_k_ia = dir[0] * dir[2] * cos_ia;
+ j_k_ia = dir[1] * dir[2] * cos_ia;
+
+ a_sin = dir[0] * sin_a;
+ b_sin = dir[1] * sin_a;
+ c_sin = dir[2] * sin_a;
+
+ rot[0][0] = i_i_ia + cos_a;
+ rot[0][1] = i_j_ia - c_sin;
+ rot[0][2] = i_k_ia + b_sin;
+ rot[1][0] = i_j_ia + c_sin;
+ rot[1][1] = j_j_ia + cos_a;
+ rot[1][2] = j_k_ia - a_sin;
+ rot[2][0] = i_k_ia - b_sin;
+ rot[2][1] = j_k_ia + a_sin;
+ rot[2][2] = k_k_ia + cos_a;
+
+ dst[0] = point[0] * rot[0][0] + point[1] * rot[0][1] + point[2] * rot[0][2];
+ dst[1] = point[0] * rot[1][0] + point[1] * rot[1][1] + point[2] * rot[1][2];
+ dst[2] = point[0] * rot[2][0] + point[1] * rot[2][1] + point[2] * rot[2][2];
+}
+
+/*
+===============
+RotateAroundDirection
+===============
+*/
+void RotateAroundDirection( vec3_t axis[3], vec_t angle ) {
+ vec_t scale;
+
+ angle = DEG2RAD( angle );
+
+ // create an arbitrary axis[1]
+ PerpendicularVector( axis[ 1 ], axis[ 0 ] );
+
+ // cross to get axis[2]
+ CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] );
+
+ // rotate
+ scale = cos( angle );
+ VectorScale( axis[ 1 ], scale, axis[ 1 ] );
+
+ scale = sin( angle );
+ VectorMA( axis[ 1 ], scale, axis[ 2 ], axis[ 1 ] );
+
+ // recalculate axis[2]
+ CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] );
+}
+
+
+
+void vectoangles( const vec3_t value1, vec3_t angles ) {
+ float forward;
+ float yaw, pitch;
+
+ if ( value1[1] == 0 && value1[0] == 0 ) {
+ yaw = 0;
+ if ( value1[2] > 0 ) {
+ pitch = 90;
+ }
+ else {
+ pitch = 270;
+ }
+ }
+ else {
+ if ( value1[0] ) {
+ yaw = ( atan2 ( value1[1], value1[0] ) * 180 / M_PI );
+ }
+ else if ( value1[1] > 0 ) {
+ yaw = 90;
+ }
+ else {
+ yaw = 270;
+ }
+ if ( yaw < 0 ) {
+ yaw += 360;
+ }
+
+ forward = sqrt ( value1[0]*value1[0] + value1[1]*value1[1] );
+ pitch = ( atan2(value1[2], forward) * 180 / M_PI );
+ if ( pitch < 0 ) {
+ pitch += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = 0;
+}
+
+
+/*
+=================
+AxisToAngles
+
+Takes an axis (forward + right + up)
+and returns angles -- including a roll
+=================
+*/
+void AxisToAngles( vec3_t axis[3], vec3_t angles ) {
+ float length1;
+ float yaw, pitch, roll = 0.0f;
+
+ if ( axis[0][1] == 0 && axis[0][0] == 0 ) {
+ yaw = 0;
+ if ( axis[0][2] > 0 ) {
+ pitch = 90;
+ }
+ else {
+ pitch = 270;
+ }
+ }
+ else {
+ if ( axis[0][0] ) {
+ yaw = ( atan2 ( axis[0][1], axis[0][0] ) * 180 / M_PI );
+ }
+ else if ( axis[0][1] > 0 ) {
+ yaw = 90;
+ }
+ else {
+ yaw = 270;
+ }
+ if ( yaw < 0 ) {
+ yaw += 360;
+ }
+
+ length1 = sqrt ( axis[0][0]*axis[0][0] + axis[0][1]*axis[0][1] );
+ pitch = ( atan2(axis[0][2], length1) * 180 / M_PI );
+ if ( pitch < 0 ) {
+ pitch += 360;
+ }
+
+ roll = ( atan2( axis[1][2], axis[2][2] ) * 180 / M_PI );
+ if ( roll < 0 ) {
+ roll += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = roll;
+}
+
+/*
+=================
+AnglesToAxis
+=================
+*/
+void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ) {
+ vec3_t right;
+
+ // angle vectors returns "right" instead of "y axis"
+ AngleVectors( angles, axis[0], right, axis[2] );
+ VectorSubtract( vec3_origin, right, axis[1] );
+}
+
+void AxisClear( vec3_t axis[3] ) {
+ axis[0][0] = 1;
+ axis[0][1] = 0;
+ axis[0][2] = 0;
+ axis[1][0] = 0;
+ axis[1][1] = 1;
+ axis[1][2] = 0;
+ axis[2][0] = 0;
+ axis[2][1] = 0;
+ axis[2][2] = 1;
+}
+
+void AxisCopy( vec3_t in[3], vec3_t out[3] ) {
+ VectorCopy( in[0], out[0] );
+ VectorCopy( in[1], out[1] );
+ VectorCopy( in[2], out[2] );
+}
+
+void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal )
+{
+ float d;
+ vec3_t n;
+ float inv_denom;
+
+ inv_denom = 1.0f / DotProduct( normal, normal );
+#ifndef Q3_VM
+ assert( Q_fabs(inv_denom) != 0.0f ); // bk010122 - zero vectors get here
+#endif
+ inv_denom = 1.0f / inv_denom;
+
+ d = DotProduct( normal, p ) * inv_denom;
+
+ n[0] = normal[0] * inv_denom;
+ n[1] = normal[1] * inv_denom;
+ n[2] = normal[2] * inv_denom;
+
+ dst[0] = p[0] - d * n[0];
+ dst[1] = p[1] - d * n[1];
+ dst[2] = p[2] - d * n[2];
+}
+
+/*
+================
+MakeNormalVectors
+
+Given a normalized forward vector, create two
+other perpendicular vectors
+================
+*/
+void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up) {
+ float d;
+
+ // this rotate and negate guarantees a vector
+ // not colinear with the original
+ right[1] = -forward[0];
+ right[2] = forward[1];
+ right[0] = forward[2];
+
+ d = DotProduct (right, forward);
+ VectorMA (right, -d, forward, right);
+ VectorNormalize (right);
+ CrossProduct (right, forward, up);
+}
+
+
+void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out )
+{
+ out[0] = DotProduct( in, matrix[0] );
+ out[1] = DotProduct( in, matrix[1] );
+ out[2] = DotProduct( in, matrix[2] );
+}
+
+//============================================================================
+
+#if !idppc
+/*
+** float q_rsqrt( float number )
+*/
+float Q_rsqrt( float number )
+{
+ union {
+ float f;
+ int i;
+ } t;
+ float x2, y;
+ const float threehalfs = 1.5F;
+
+ x2 = number * 0.5F;
+ t.f = number;
+ t.i = 0x5f3759df - ( t.i >> 1 ); // what the fuck?
+ y = t.f;
+ y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
+// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
+
+ //assert( !isnan(y) ); // bk010122 - FPE?
+ return y;
+}
+
+float Q_fabs( float f ) {
+ int tmp = * ( int * ) &f;
+ tmp &= 0x7FFFFFFF;
+ return * ( float * ) &tmp;
+}
+#endif
+
+//============================================================
+
+/*
+===============
+LerpAngle
+
+===============
+*/
+float LerpAngle (float from, float to, float frac) {
+ float a;
+
+ if ( to - from > 180 ) {
+ to -= 360;
+ }
+ if ( to - from < -180 ) {
+ to += 360;
+ }
+ a = from + frac * (to - from);
+
+ return a;
+}
+
+
+/*
+=================
+AngleSubtract
+
+Always returns a value from -180 to 180
+=================
+*/
+float AngleSubtract( float a1, float a2 ) {
+ float a;
+
+ a = a1 - a2;
+ while ( a > 180 ) {
+ a -= 360;
+ }
+ while ( a < -180 ) {
+ a += 360;
+ }
+ return a;
+}
+
+
+void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ) {
+ v3[0] = AngleSubtract( v1[0], v2[0] );
+ v3[1] = AngleSubtract( v1[1], v2[1] );
+ v3[2] = AngleSubtract( v1[2], v2[2] );
+}
+
+
+float AngleMod(float a) {
+ a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535);
+ return a;
+}
+
+
+/*
+=================
+AngleNormalize360
+
+returns angle normalized to the range [0 <= angle < 360]
+=================
+*/
+float AngleNormalize360 ( float angle ) {
+ return (360.0 / 65536) * ((int)(angle * (65536 / 360.0)) & 65535);
+}
+
+
+/*
+=================
+AngleNormalize180
+
+returns angle normalized to the range [-180 < angle <= 180]
+=================
+*/
+float AngleNormalize180 ( float angle ) {
+ angle = AngleNormalize360( angle );
+ if ( angle > 180.0 ) {
+ angle -= 360.0;
+ }
+ return angle;
+}
+
+
+/*
+=================
+AngleDelta
+
+returns the normalized delta from angle1 to angle2
+=================
+*/
+float AngleDelta ( float angle1, float angle2 ) {
+ return AngleNormalize180( angle1 - angle2 );
+}
+
+
+//============================================================
+
+
+/*
+=================
+SetPlaneSignbits
+=================
+*/
+void SetPlaneSignbits (cplane_t *out) {
+ int bits, j;
+
+ // for fast box on planeside test
+ bits = 0;
+ for (j=0 ; j<3 ; j++) {
+ if (out->normal[j] < 0) {
+ bits |= 1<<j;
+ }
+ }
+ out->signbits = bits;
+}
+
+
+/*
+==================
+BoxOnPlaneSide
+
+Returns 1, 2, or 1 + 2
+
+// this is the slow, general version
+int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ int i;
+ float dist1, dist2;
+ int sides;
+ vec3_t corners[2];
+
+ for (i=0 ; i<3 ; i++)
+ {
+ if (p->normal[i] < 0)
+ {
+ corners[0][i] = emins[i];
+ corners[1][i] = emaxs[i];
+ }
+ else
+ {
+ corners[1][i] = emins[i];
+ corners[0][i] = emaxs[i];
+ }
+ }
+ dist1 = DotProduct (p->normal, corners[0]) - p->dist;
+ dist2 = DotProduct (p->normal, corners[1]) - p->dist;
+ sides = 0;
+ if (dist1 >= 0)
+ sides = 1;
+ if (dist2 < 0)
+ sides |= 2;
+
+ return sides;
+}
+
+==================
+*/
+
+#if !id386
+
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ float dist1, dist2;
+ int sides;
+
+// fast axial cases
+ if (p->type < 3)
+ {
+ if (p->dist <= emins[p->type])
+ return 1;
+ if (p->dist >= emaxs[p->type])
+ return 2;
+ return 3;
+ }
+
+// general case
+ switch (p->signbits)
+ {
+ case 0:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ break;
+ case 1:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ break;
+ case 2:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ break;
+ case 3:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ break;
+ case 4:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ break;
+ case 5:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ break;
+ case 6:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ break;
+ case 7:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ break;
+ default:
+ dist1 = dist2 = 0; // shut up compiler
+ break;
+ }
+
+ sides = 0;
+ if (dist1 >= p->dist)
+ sides = 1;
+ if (dist2 < p->dist)
+ sides |= 2;
+
+ return sides;
+}
+#elif __GNUC__
+// use matha.s
+#else
+#pragma warning( disable: 4035 )
+
+__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ static int bops_initialized;
+ static int Ljmptab[8];
+
+ __asm {
+
+ push ebx
+
+ cmp bops_initialized, 1
+ je initialized
+ mov bops_initialized, 1
+
+ mov Ljmptab[0*4], offset Lcase0
+ mov Ljmptab[1*4], offset Lcase1
+ mov Ljmptab[2*4], offset Lcase2
+ mov Ljmptab[3*4], offset Lcase3
+ mov Ljmptab[4*4], offset Lcase4
+ mov Ljmptab[5*4], offset Lcase5
+ mov Ljmptab[6*4], offset Lcase6
+ mov Ljmptab[7*4], offset Lcase7
+
+initialized:
+
+ mov edx,dword ptr[4+12+esp]
+ mov ecx,dword ptr[4+4+esp]
+ xor eax,eax
+ mov ebx,dword ptr[4+8+esp]
+ mov al,byte ptr[17+edx]
+ cmp al,8
+ jge Lerror
+ fld dword ptr[0+edx]
+ fld st(0)
+ jmp dword ptr[Ljmptab+eax*4]
+Lcase0:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase1:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase2:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase3:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase4:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase5:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase6:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase7:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+LSetSides:
+ faddp st(2),st(0)
+ fcomp dword ptr[12+edx]
+ xor ecx,ecx
+ fnstsw ax
+ fcomp dword ptr[12+edx]
+ and ah,1
+ xor ah,1
+ add cl,ah
+ fnstsw ax
+ and ah,1
+ add ah,ah
+ add cl,ah
+ pop ebx
+ mov eax,ecx
+ ret
+Lerror:
+ int 3
+ }
+}
+#pragma warning( default: 4035 )
+
+#endif
+
+/*
+=================
+RadiusFromBounds
+=================
+*/
+float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) {
+ int i;
+ vec3_t corner;
+ float a, b;
+
+ for (i=0 ; i<3 ; i++) {
+ a = fabs( mins[i] );
+ b = fabs( maxs[i] );
+ corner[i] = a > b ? a : b;
+ }
+
+ return VectorLength (corner);
+}
+
+
+void ClearBounds( vec3_t mins, vec3_t maxs ) {
+ mins[0] = mins[1] = mins[2] = 99999;
+ maxs[0] = maxs[1] = maxs[2] = -99999;
+}
+
+void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) {
+ if ( v[0] < mins[0] ) {
+ mins[0] = v[0];
+ }
+ if ( v[0] > maxs[0]) {
+ maxs[0] = v[0];
+ }
+
+ if ( v[1] < mins[1] ) {
+ mins[1] = v[1];
+ }
+ if ( v[1] > maxs[1]) {
+ maxs[1] = v[1];
+ }
+
+ if ( v[2] < mins[2] ) {
+ mins[2] = v[2];
+ }
+ if ( v[2] > maxs[2]) {
+ maxs[2] = v[2];
+ }
+}
+
+
+vec_t VectorNormalize( vec3_t v ) {
+ // NOTE: TTimo - Apple G4 altivec source uses double?
+ float length, ilength;
+
+ length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+ length = sqrt (length);
+
+ if ( length ) {
+ ilength = 1/length;
+ v[0] *= ilength;
+ v[1] *= ilength;
+ v[2] *= ilength;
+ }
+
+ return length;
+}
+
+vec_t VectorNormalize2( const vec3_t v, vec3_t out) {
+ float length, ilength;
+
+ length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+ length = sqrt (length);
+
+ if (length)
+ {
+#ifndef Q3_VM // bk0101022 - FPE related
+// assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) );
+#endif
+ ilength = 1/length;
+ out[0] = v[0]*ilength;
+ out[1] = v[1]*ilength;
+ out[2] = v[2]*ilength;
+ } else {
+#ifndef Q3_VM // bk0101022 - FPE related
+// assert( ((Q_fabs(v[0])==0.0f) && (Q_fabs(v[1])==0.0f) && (Q_fabs(v[2])==0.0f)) );
+#endif
+ VectorClear( out );
+ }
+
+ return length;
+
+}
+
+void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) {
+ vecc[0] = veca[0] + scale*vecb[0];
+ vecc[1] = veca[1] + scale*vecb[1];
+ vecc[2] = veca[2] + scale*vecb[2];
+}
+
+
+vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ) {
+ return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
+}
+
+void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ) {
+ out[0] = veca[0]-vecb[0];
+ out[1] = veca[1]-vecb[1];
+ out[2] = veca[2]-vecb[2];
+}
+
+void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ) {
+ out[0] = veca[0]+vecb[0];
+ out[1] = veca[1]+vecb[1];
+ out[2] = veca[2]+vecb[2];
+}
+
+void _VectorCopy( const vec3_t in, vec3_t out ) {
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+}
+
+void _VectorScale( const vec3_t in, vec_t scale, vec3_t out ) {
+ out[0] = in[0]*scale;
+ out[1] = in[1]*scale;
+ out[2] = in[2]*scale;
+}
+
+void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ) {
+ out[0] = in[0]*scale;
+ out[1] = in[1]*scale;
+ out[2] = in[2]*scale;
+ out[3] = in[3]*scale;
+}
+
+
+int Q_log2( int val ) {
+ int answer;
+
+ answer = 0;
+ while ( ( val>>=1 ) != 0 ) {
+ answer++;
+ }
+ return answer;
+}
+
+
+
+/*
+=================
+PlaneTypeForNormal
+=================
+*/
+/*
+int PlaneTypeForNormal (vec3_t normal) {
+ if ( normal[0] == 1.0 )
+ return PLANE_X;
+ if ( normal[1] == 1.0 )
+ return PLANE_Y;
+ if ( normal[2] == 1.0 )
+ return PLANE_Z;
+
+ return PLANE_NON_AXIAL;
+}
+*/
+
+
+/*
+================
+MatrixMultiply
+================
+*/
+void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]) {
+ out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+ in1[0][2] * in2[2][0];
+ out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+ in1[0][2] * in2[2][1];
+ out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+ in1[0][2] * in2[2][2];
+ out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+ in1[1][2] * in2[2][0];
+ out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+ in1[1][2] * in2[2][1];
+ out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+ in1[1][2] * in2[2][2];
+ out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+ in1[2][2] * in2[2][0];
+ out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+ in1[2][2] * in2[2][1];
+ out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+ in1[2][2] * in2[2][2];
+}
+
+/*
+================
+VectorMatrixMultiply
+================
+*/
+void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out )
+{
+ out[ 0 ] = m[ 0 ][ 0 ] * p[ 0 ] + m[ 1 ][ 0 ] * p[ 1 ] + m[ 2 ][ 0 ] * p[ 2 ];
+ out[ 1 ] = m[ 0 ][ 1 ] * p[ 0 ] + m[ 1 ][ 1 ] * p[ 1 ] + m[ 2 ][ 1 ] * p[ 2 ];
+ out[ 2 ] = m[ 0 ][ 2 ] * p[ 0 ] + m[ 1 ][ 2 ] * p[ 1 ] + m[ 2 ][ 2 ] * p[ 2 ];
+}
+
+
+void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) {
+ float angle;
+ static float sr, sp, sy, cr, cp, cy;
+ // static to help MS compiler fp bugs
+
+ angle = angles[YAW] * (M_PI*2 / 360);
+ sy = sin(angle);
+ cy = cos(angle);
+ angle = angles[PITCH] * (M_PI*2 / 360);
+ sp = sin(angle);
+ cp = cos(angle);
+ angle = angles[ROLL] * (M_PI*2 / 360);
+ sr = sin(angle);
+ cr = cos(angle);
+
+ if (forward)
+ {
+ forward[0] = cp*cy;
+ forward[1] = cp*sy;
+ forward[2] = -sp;
+ }
+ if (right)
+ {
+ right[0] = (-1*sr*sp*cy+-1*cr*-sy);
+ right[1] = (-1*sr*sp*sy+-1*cr*cy);
+ right[2] = -1*sr*cp;
+ }
+ if (up)
+ {
+ up[0] = (cr*sp*cy+-sr*-sy);
+ up[1] = (cr*sp*sy+-sr*cy);
+ up[2] = cr*cp;
+ }
+}
+
+/*
+** assumes "src" is normalized
+*/
+void PerpendicularVector( vec3_t dst, const vec3_t src )
+{
+ int pos;
+ int i;
+ float minelem = 1.0F;
+ vec3_t tempvec;
+
+ /*
+ ** find the smallest magnitude axially aligned vector
+ */
+ for ( pos = 0, i = 0; i < 3; i++ )
+ {
+ if ( fabs( src[i] ) < minelem )
+ {
+ pos = i;
+ minelem = fabs( src[i] );
+ }
+ }
+ tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
+ tempvec[pos] = 1.0F;
+
+ /*
+ ** project the point onto the plane defined by src
+ */
+ ProjectPointOnPlane( dst, tempvec, src );
+
+ /*
+ ** normalize the result
+ */
+ VectorNormalize( dst );
+}
+
+/*
+=================
+pointToLineDistance
+
+Distance from a point to some line
+=================
+*/
+float pointToLineDistance( const vec3_t p0, const vec3_t p1, const vec3_t p2 )
+{
+ vec3_t v, w, y;
+ float c1, c2;
+
+ VectorSubtract( p2, p1, v );
+ VectorSubtract( p1, p0, w );
+
+ CrossProduct( w, v, y );
+ c1 = VectorLength( y );
+ c2 = VectorLength( v );
+
+ if( c2 == 0.0f )
+ return 0.0f;
+ else
+ return c1 / c2;
+}
+
+/*
+=================
+GetPerpendicularViewVector
+
+Used to find an "up" vector for drawing a sprite so that it always faces the view as best as possible
+=================
+*/
+void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, const vec3_t p2, vec3_t up )
+{
+ vec3_t v1, v2;
+
+ VectorSubtract( point, p1, v1 );
+ VectorNormalize( v1 );
+
+ VectorSubtract( point, p2, v2 );
+ VectorNormalize( v2 );
+
+ CrossProduct( v1, v2, up );
+ VectorNormalize( up );
+}
+
+/*
+================
+ProjectPointOntoVector
+================
+*/
+void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj )
+{
+ vec3_t pVec, vec;
+
+ VectorSubtract( point, vStart, pVec );
+ VectorSubtract( vEnd, vStart, vec );
+ VectorNormalize( vec );
+ // project onto the directional vector for this segment
+ VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj );
+}
+
+/*
+================
+VectorMaxComponent
+
+Return the biggest component of some vector
+================
+*/
+float VectorMaxComponent( vec3_t v )
+{
+ float biggest = v[ 0 ];
+
+ if( v[ 1 ] > biggest )
+ biggest = v[ 1 ];
+
+ if( v[ 2 ] > biggest )
+ biggest = v[ 2 ];
+
+ return biggest;
+}
+
+/*
+================
+VectorMinComponent
+
+Return the smallest component of some vector
+================
+*/
+float VectorMinComponent( vec3_t v )
+{
+ float smallest = v[ 0 ];
+
+ if( v[ 1 ] < smallest )
+ smallest = v[ 1 ];
+
+ if( v[ 2 ] < smallest )
+ smallest = v[ 2 ];
+
+ return smallest;
+}
+
+
+#define LINE_DISTANCE_EPSILON 1e-05f
+
+/*
+================
+DistanceBetweenLineSegmentsSquared
+
+Return the smallest distance between two line segments, squared
+================
+*/
+vec_t DistanceBetweenLineSegmentsSquared(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t )
+{
+ vec3_t sMag, tMag, diff;
+ float a, b, c, d, e;
+ float D;
+ float sN, sD;
+ float tN, tD;
+ vec3_t separation;
+
+ VectorSubtract( sP1, sP0, sMag );
+ VectorSubtract( tP1, tP0, tMag );
+ VectorSubtract( sP0, tP0, diff );
+ a = DotProduct( sMag, sMag );
+ b = DotProduct( sMag, tMag );
+ c = DotProduct( tMag, tMag );
+ d = DotProduct( sMag, diff );
+ e = DotProduct( tMag, diff );
+ sD = tD = D = a * c - b * b;
+
+ if( D < LINE_DISTANCE_EPSILON )
+ {
+ // the lines are almost parallel
+ sN = 0.0; // force using point P0 on segment S1
+ sD = 1.0; // to prevent possible division by 0.0 later
+ tN = e;
+ tD = c;
+ }
+ else
+ {
+ // get the closest points on the infinite lines
+ sN = ( b * e - c * d );
+ tN = ( a * e - b * d );
+
+ if( sN < 0.0 )
+ {
+ // sN < 0 => the s=0 edge is visible
+ sN = 0.0;
+ tN = e;
+ tD = c;
+ }
+ else if( sN > sD )
+ {
+ // sN > sD => the s=1 edge is visible
+ sN = sD;
+ tN = e + b;
+ tD = c;
+ }
+ }
+
+ if( tN < 0.0 )
+ {
+ // tN < 0 => the t=0 edge is visible
+ tN = 0.0;
+
+ // recompute sN for this edge
+ if( -d < 0.0 )
+ sN = 0.0;
+ else if( -d > a )
+ sN = sD;
+ else
+ {
+ sN = -d;
+ sD = a;
+ }
+ }
+ else if( tN > tD )
+ {
+ // tN > tD => the t=1 edge is visible
+ tN = tD;
+
+ // recompute sN for this edge
+ if( ( -d + b ) < 0.0 )
+ sN = 0;
+ else if( ( -d + b ) > a )
+ sN = sD;
+ else
+ {
+ sN = ( -d + b );
+ sD = a;
+ }
+ }
+
+ // finally do the division to get *s and *t
+ *s = ( fabs( sN ) < LINE_DISTANCE_EPSILON ? 0.0 : sN / sD );
+ *t = ( fabs( tN ) < LINE_DISTANCE_EPSILON ? 0.0 : tN / tD );
+
+ // get the difference of the two closest points
+ VectorScale( sMag, *s, sMag );
+ VectorScale( tMag, *t, tMag );
+ VectorAdd( diff, sMag, separation );
+ VectorSubtract( separation, tMag, separation );
+
+ return VectorLengthSquared( separation );
+}
+
+/*
+================
+DistanceBetweenLineSegments
+
+Return the smallest distance between two line segments
+================
+*/
+vec_t DistanceBetweenLineSegments(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t )
+{
+ return (vec_t)sqrt( DistanceBetweenLineSegmentsSquared(
+ sP0, sP1, tP0, tP1, s, t ) );
+}
+
+/*
+=================
+Q_isnan
+
+Don't pass doubles to this
+================
+*/
+int Q_isnan( float x )
+{
+ union
+ {
+ float f;
+ unsigned int i;
+ } t;
+
+ t.f = x;
+ t.i &= 0x7FFFFFFF;
+ t.i = 0x7F800000 - t.i;
+
+ return (int)( (unsigned int)t.i >> 31 );
+}
diff --git a/src/qcommon/q_platform.h b/src/qcommon/q_platform.h
new file mode 100644
index 0000000..84b87b9
--- /dev/null
+++ b/src/qcommon/q_platform.h
@@ -0,0 +1,386 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __Q_PLATFORM_H
+#define __Q_PLATFORM_H
+
+// this is for determining if we have an asm version of a C function
+#ifdef Q3_VM
+
+#define id386 0
+#define idppc 0
+#define idppc_altivec 0
+
+#else
+
+#if (defined _M_IX86 || defined __i386__) && !defined(C_ONLY)
+#define id386 1
+#else
+#define id386 0
+#endif
+
+#if (defined(powerc) || defined(powerpc) || defined(ppc) || \
+ defined(__ppc) || defined(__ppc__)) && !defined(C_ONLY)
+#define idppc 1
+#if defined(__VEC__)
+#define idppc_altivec 1
+#ifdef __APPLE__ // Apple's GCC does this differently than the FSF.
+#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \
+ (vector unsigned char) (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)
+#else
+#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \
+ (vector unsigned char) {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}
+#endif
+#else
+#define idppc_altivec 0
+#endif
+#else
+#define idppc 0
+#define idppc_altivec 0
+#endif
+
+#endif
+
+#ifndef __ASM_I386__ // don't include the C bits if included from qasm.h
+
+// for windows fastcall option
+#define QDECL
+
+//================================================================= WIN64/32 ===
+
+#if defined(_WIN64) || defined(__WIN64__)
+
+ #undef idx64
+ #define idx64 1
+
+ #undef QDECL
+ #define QDECL __cdecl
+
+ #undef QCALL
+ #define QCALL __stdcall
+
+ #if defined( _MSC_VER )
+ #define OS_STRING "win_msvc64"
+ #elif defined __MINGW64__
+ #define OS_STRING "win_mingw64"
+ #endif
+
+ #define ID_INLINE __inline
+ #define PATH_SEP '\\'
+
+ #if defined( __WIN64__ )
+ #define ARCH_STRING "x86_64"
+ #elif defined _M_ALPHA
+ #define ARCH_STRING "AXP"
+ #endif
+
+ #define Q3_LITTLE_ENDIAN
+
+ #define DLL_EXT ".dll"
+ #define EXE_EXT ".exe"
+
+ // For cl_updates.cpp
+ #define RELEASE_PACKAGE_NAME ( "release-mingw32-" ARCH_STRING ".zip" )
+
+#elif defined(_WIN32) || defined(__WIN32__)
+
+ #undef QDECL
+ #define QDECL __cdecl
+
+ #undef QCALL
+ #define QCALL __stdcall
+
+ #if defined( _MSC_VER )
+ #define OS_STRING "win_msvc"
+ #elif defined __MINGW32__
+ #define OS_STRING "win_mingw"
+ #endif
+
+ #define ID_INLINE __inline
+ #define PATH_SEP '\\'
+
+ #if defined( _M_IX86 ) || defined( __i386__ )
+ #define ARCH_STRING "x86"
+ #elif defined _M_ALPHA
+ #define ARCH_STRING "AXP"
+ #endif
+
+ #define Q3_LITTLE_ENDIAN
+
+ #define DLL_EXT ".dll"
+ #define EXE_EXT ".exe"
+
+ // For cl_updates.cpp
+ #define RELEASE_PACKAGE_NAME ( "release-mingw32-" ARCH_STRING ".zip" )
+
+#endif
+
+
+//============================================================== MAC OS X ===
+
+#if defined(__APPLE__) || defined(__APPLE_CC__)
+
+ #define OS_STRING "macosx"
+ #define ID_INLINE inline
+ #define PATH_SEP '/'
+
+ #ifdef __ppc__
+ #define ARCH_STRING "ppc"
+ #define Q3_BIG_ENDIAN
+ #elif defined __i386__
+ #define ARCH_STRING "x86"
+ #define Q3_LITTLE_ENDIAN
+ #elif defined __x86_64__
+ #undef idx64
+ #define idx64 1
+ #define ARCH_STRING "x86_64"
+ #define Q3_LITTLE_ENDIAN
+ #endif
+
+ #define DLL_EXT ".dylib"
+ #define EXE_EXT
+
+ // For cl_updates.cpp
+ #define RELEASE_PACKAGE_NAME ( "release-darwin-" ARCH_STRING ".zip" )
+
+#endif
+
+//================================================================= LINUX ===
+
+
+#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__GNU__)
+
+ #include <endian.h>
+
+ #if defined(__linux__)
+ # define OS_STRING "linux"
+ #elif defined(__FreeBSD_kernel__)
+ # define OS_STRING "kFreeBSD"
+ #else
+ # define OS_STRING "GNU"
+ #endif
+
+ #define ID_INLINE inline
+ #define PATH_SEP '/'
+
+ #ifdef __ppc__
+ #define ARCH_STRING "ppc"
+ #define Q3_BIG_ENDIAN
+ #elif defined __i386__
+ # define ARCH_STRING "x86"
+ # define Q3_LITTLE_ENDIAN
+ #elif defined __x86_64__
+ # define ARCH_STRING "x86_64"
+ # define Q3_LITTLE_ENDIAN
+ #undef idx64
+ #define idx64 1
+ #endif
+
+ #define DLL_EXT ".so"
+ #define EXE_EXT
+
+ // For cl_updates.cpp
+ #define RELEASE_PACKAGE_NAME ( "release-linux-" ARCH_STRING ".zip" )
+
+#endif
+
+//=================================================================== BSD ===
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+
+ #include <sys/types.h>
+ #include <machine/endian.h>
+
+ #ifndef __BSD__
+ #define __BSD__
+ #endif
+
+ #if defined(__FreeBSD__)
+ #define OS_STRING "freebsd"
+ #elif defined(__OpenBSD__)
+ #define OS_STRING "openbsd"
+ #elif defined(__NetBSD__)
+ #define OS_STRING "netbsd"
+ #endif
+
+ #define ID_INLINE inline
+ #define PATH_SEP '/'
+
+ #ifdef __i386__
+ #define ARCH_STRING "x86"
+ #elif defined __amd64__
+ #undef idx64
+ #define idx64 1
+ #define ARCH_STRING "x86_64"
+ #elif defined __axp__
+ #define ARCH_STRING "alpha"
+ #endif
+
+ #if BYTE_ORDER == BIG_ENDIAN
+ #define Q3_BIG_ENDIAN
+ #else
+ #define Q3_LITTLE_ENDIAN
+ #endif
+
+ #define DLL_EXT ".so"
+ #define EXE_EXT
+
+#endif
+
+//================================================================= SUNOS ===
+
+#ifdef __sun
+
+ #include <stdint.h>
+ #include <sys/byteorder.h>
+
+ #define OS_STRING "solaris"
+ #define ID_INLINE inline
+ #define PATH_SEP '/'
+
+ #ifdef __i386__
+ #define ARCH_STRING "x86"
+ #elif defined __sparc
+ #define ARCH_STRING "sparc"
+ #endif
+
+ #if defined( _BIG_ENDIAN )
+ #define Q3_BIG_ENDIAN
+ #elif defined( _LITTLE_ENDIAN )
+ #define Q3_LITTLE_ENDIAN
+ #endif
+
+ #define DLL_EXT ".so"
+ #define EXE_EXT
+
+#endif
+
+//================================================================== IRIX ===
+
+#ifdef __sgi
+
+ #define OS_STRING "irix"
+ #define ID_INLINE __inline
+ #define PATH_SEP '/'
+
+ #define ARCH_STRING "mips"
+
+ #define Q3_BIG_ENDIAN // SGI's MIPS are always big endian
+
+ #define DLL_EXT ".so"
+ #define EXE_EXT
+
+#endif
+
+//================================================================== Q3VM ===
+
+#ifdef Q3_VM
+
+ #define OS_STRING "q3vm"
+ #define ID_INLINE
+ #define PATH_SEP '/'
+
+ #define ARCH_STRING "bytecode"
+
+ #define DLL_EXT ".qvm"
+ #define EXE_EXT
+
+#endif
+
+//===========================================================================
+
+//catch missing defines in above blocks
+#if !defined( OS_STRING )
+#error "Operating system not supported"
+#endif
+
+#if !defined( ARCH_STRING )
+#error "Architecture not supported"
+#endif
+
+#ifndef ID_INLINE
+#error "ID_INLINE not defined"
+#endif
+
+#ifndef PATH_SEP
+#error "PATH_SEP not defined"
+#endif
+
+#ifndef DLL_EXT
+#error "DLL_EXT not defined"
+#endif
+
+#ifndef EXE_EXT
+#error "EXE_EXT not defined"
+#endif
+
+
+//endianness
+short ShortSwap (short l);
+int LongSwap (int l);
+float FloatSwap (const float *f);
+
+#if defined( Q3_BIG_ENDIAN ) && defined( Q3_LITTLE_ENDIAN )
+#error "Endianness defined as both big and little"
+#elif defined( Q3_BIG_ENDIAN )
+
+#define LittleShort(x) ShortSwap(x)
+#define LittleLong(x) LongSwap(x)
+#define LittleFloat(x) FloatSwap(&x)
+#define BigShort
+#define BigLong
+#define BigFloat
+
+#elif defined( Q3_LITTLE_ENDIAN )
+
+#define LittleShort
+#define LittleLong
+#define LittleFloat
+#define BigShort(x) ShortSwap(x)
+#define BigLong(x) LongSwap(x)
+#define BigFloat(x) FloatSwap(&x)
+
+#elif defined( Q3_VM )
+
+#define LittleShort
+#define LittleLong
+#define LittleFloat
+#define BigShort
+#define BigLong
+#define BigFloat
+
+#else
+#error "Endianness not defined"
+#endif
+
+
+//platform string
+#ifdef NDEBUG
+#define PLATFORM_STRING OS_STRING "-" ARCH_STRING
+#else
+#define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug"
+#endif
+
+#endif
+
+#endif
diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c
new file mode 100644
index 0000000..2ac5537
--- /dev/null
+++ b/src/qcommon/q_shared.c
@@ -0,0 +1,1347 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+// q_shared.c -- stateless support routines that are included in each code dll
+#include "q_shared.h"
+
+float Com_Clamp( float min, float max, float value ) {
+ if ( value < min ) {
+ return min;
+ }
+ if ( value > max ) {
+ return max;
+ }
+ return value;
+}
+
+
+/*
+============
+COM_SkipPath
+============
+*/
+char *COM_SkipPath (char *pathname)
+{
+ char *last;
+
+ last = pathname;
+ while (*pathname)
+ {
+ if (*pathname=='/')
+ last = pathname+1;
+ pathname++;
+ }
+ return last;
+}
+
+/*
+============
+COM_StripExtension
+============
+*/
+void COM_StripExtension( const char *in, char *out, int destsize ) {
+ int length;
+
+ Q_strncpyz(out, in, destsize);
+
+ length = strlen(out)-1;
+ while (length > 0 && out[length] != '.')
+ {
+ length--;
+ if (out[length] == '/')
+ return; // no extension
+ }
+ if (length)
+ out[length] = 0;
+}
+
+
+/*
+==================
+COM_DefaultExtension
+==================
+*/
+void COM_DefaultExtension (char *path, int maxSize, const char *extension ) {
+ char oldPath[MAX_QPATH];
+ char *src;
+
+//
+// if path doesn't have a .EXT, append extension
+// (extension should include the .)
+//
+ src = path + strlen(path) - 1;
+
+ while (*src != '/' && src != path) {
+ if ( *src == '.' ) {
+ return; // it has an extension
+ }
+ src--;
+ }
+
+ Q_strncpyz( oldPath, path, sizeof( oldPath ) );
+ Com_sprintf( path, maxSize, "%s%s", oldPath, extension );
+}
+
+/*
+============================================================================
+
+ BYTE ORDER FUNCTIONS
+
+============================================================================
+*/
+/*
+// can't just use function pointers, or dll linkage can
+// mess up when qcommon is included in multiple places
+static short (*_BigShort) (short l);
+static short (*_LittleShort) (short l);
+static int (*_BigLong) (int l);
+static int (*_LittleLong) (int l);
+static qint64 (*_BigLong64) (qint64 l);
+static qint64 (*_LittleLong64) (qint64 l);
+static float (*_BigFloat) (const float *l);
+static float (*_LittleFloat) (const float *l);
+
+short BigShort(short l){return _BigShort(l);}
+short LittleShort(short l) {return _LittleShort(l);}
+int BigLong (int l) {return _BigLong(l);}
+int LittleLong (int l) {return _LittleLong(l);}
+qint64 BigLong64 (qint64 l) {return _BigLong64(l);}
+qint64 LittleLong64 (qint64 l) {return _LittleLong64(l);}
+float BigFloat (const float *l) {return _BigFloat(l);}
+float LittleFloat (const float *l) {return _LittleFloat(l);}
+*/
+
+short ShortSwap (short l)
+{
+ byte b1,b2;
+
+ b1 = l&255;
+ b2 = (l>>8)&255;
+
+ return (b1<<8) + b2;
+}
+
+short ShortNoSwap (short l)
+{
+ return l;
+}
+
+int LongSwap (int l)
+{
+ byte b1,b2,b3,b4;
+
+ b1 = l&255;
+ b2 = (l>>8)&255;
+ b3 = (l>>16)&255;
+ b4 = (l>>24)&255;
+
+ return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+int LongNoSwap (int l)
+{
+ return l;
+}
+
+qint64 Long64Swap (qint64 ll)
+{
+ qint64 result;
+
+ result.b0 = ll.b7;
+ result.b1 = ll.b6;
+ result.b2 = ll.b5;
+ result.b3 = ll.b4;
+ result.b4 = ll.b3;
+ result.b5 = ll.b2;
+ result.b6 = ll.b1;
+ result.b7 = ll.b0;
+
+ return result;
+}
+
+qint64 Long64NoSwap (qint64 ll)
+{
+ return ll;
+}
+
+typedef union {
+ float f;
+ unsigned int i;
+} _FloatByteUnion;
+
+float FloatSwap (const float *f) {
+ _FloatByteUnion out;
+
+ out.f = *f;
+ out.i = LongSwap(out.i);
+
+ return out.f;
+}
+
+float FloatNoSwap (const float *f)
+{
+ return *f;
+}
+
+/*
+================
+Swap_Init
+================
+*/
+/*
+void Swap_Init (void)
+{
+ byte swaptest[2] = {1,0};
+
+// set the byte swapping variables in a portable manner
+ if ( *(short *)swaptest == 1)
+ {
+ _BigShort = ShortSwap;
+ _LittleShort = ShortNoSwap;
+ _BigLong = LongSwap;
+ _LittleLong = LongNoSwap;
+ _BigLong64 = Long64Swap;
+ _LittleLong64 = Long64NoSwap;
+ _BigFloat = FloatSwap;
+ _LittleFloat = FloatNoSwap;
+ }
+ else
+ {
+ _BigShort = ShortNoSwap;
+ _LittleShort = ShortSwap;
+ _BigLong = LongNoSwap;
+ _LittleLong = LongSwap;
+ _BigLong64 = Long64NoSwap;
+ _LittleLong64 = Long64Swap;
+ _BigFloat = FloatNoSwap;
+ _LittleFloat = FloatSwap;
+ }
+
+}
+*/
+
+/*
+============================================================================
+
+PARSING
+
+============================================================================
+*/
+
+static char com_token[MAX_TOKEN_CHARS];
+static char com_parsename[MAX_TOKEN_CHARS];
+static int com_lines;
+
+void COM_BeginParseSession( const char *name )
+{
+ com_lines = 0;
+ Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name);
+}
+
+int COM_GetCurrentParseLine( void )
+{
+ return com_lines;
+}
+
+char *COM_Parse( char **data_p )
+{
+ return COM_ParseExt( data_p, qtrue );
+}
+
+void COM_ParseError( char *format, ... )
+{
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string);
+}
+
+void COM_ParseWarning( char *format, ... )
+{
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string);
+}
+
+/*
+==============
+COM_Parse
+
+Parse a token out of a string
+Will never return NULL, just empty strings
+
+If "allowLineBreaks" is qtrue then an empty
+string will be returned if the next token is
+a newline.
+==============
+*/
+static char *SkipWhitespace( char *data, qboolean *hasNewLines ) {
+ int c;
+
+ while( (c = *data) <= ' ') {
+ if( !c ) {
+ return NULL;
+ }
+ if( c == '\n' ) {
+ com_lines++;
+ *hasNewLines = qtrue;
+ }
+ data++;
+ }
+
+ return data;
+}
+
+int COM_Compress( char *data_p ) {
+ char *in, *out;
+ int c;
+ qboolean newline = qfalse, whitespace = qfalse;
+
+ in = out = data_p;
+ if (in) {
+ while ((c = *in) != 0) {
+ // skip double slash comments
+ if ( c == '/' && in[1] == '/' ) {
+ while (*in && *in != '\n') {
+ in++;
+ }
+ // skip /* */ comments
+ } else if ( c == '/' && in[1] == '*' ) {
+ while ( *in && ( *in != '*' || in[1] != '/' ) )
+ in++;
+ if ( *in )
+ in += 2;
+ // record when we hit a newline
+ } else if ( c == '\n' || c == '\r' ) {
+ newline = qtrue;
+ in++;
+ // record when we hit whitespace
+ } else if ( c == ' ' || c == '\t') {
+ whitespace = qtrue;
+ in++;
+ // an actual token
+ } else {
+ // if we have a pending newline, emit it (and it counts as whitespace)
+ if (newline) {
+ *out++ = '\n';
+ newline = qfalse;
+ whitespace = qfalse;
+ } if (whitespace) {
+ *out++ = ' ';
+ whitespace = qfalse;
+ }
+
+ // copy quoted strings unmolested
+ if (c == '"') {
+ *out++ = c;
+ in++;
+ while (1) {
+ c = *in;
+ if (c && c != '"') {
+ *out++ = c;
+ in++;
+ } else {
+ break;
+ }
+ }
+ if (c == '"') {
+ *out++ = c;
+ in++;
+ }
+ } else {
+ *out = c;
+ out++;
+ in++;
+ }
+ }
+ }
+ }
+ *out = 0;
+ return out - data_p;
+}
+
+char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
+{
+ int c = 0, len;
+ qboolean hasNewLines = qfalse;
+ char *data;
+
+ data = *data_p;
+ len = 0;
+ com_token[0] = 0;
+
+ // make sure incoming data is valid
+ if ( !data )
+ {
+ *data_p = NULL;
+ return com_token;
+ }
+
+ while ( 1 )
+ {
+ // skip whitespace
+ data = SkipWhitespace( data, &hasNewLines );
+ if ( !data )
+ {
+ *data_p = NULL;
+ return com_token;
+ }
+ if ( hasNewLines && !allowLineBreaks )
+ {
+ *data_p = data;
+ return com_token;
+ }
+
+ c = *data;
+
+ // skip double slash comments
+ if ( c == '/' && data[1] == '/' )
+ {
+ data += 2;
+ while (*data && *data != '\n') {
+ data++;
+ }
+ }
+ // skip /* */ comments
+ else if ( c=='/' && data[1] == '*' )
+ {
+ data += 2;
+ while ( *data && ( *data != '*' || data[1] != '/' ) )
+ {
+ data++;
+ }
+ if ( *data )
+ {
+ data += 2;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // handle quoted strings
+ if (c == '\"')
+ {
+ data++;
+ while (1)
+ {
+ c = *data++;
+ if (c=='\"' || !c)
+ {
+ com_token[len] = 0;
+ *data_p = ( char * ) data;
+ return com_token;
+ }
+ if (len < MAX_TOKEN_CHARS - 1)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ }
+ }
+
+ // parse a regular word
+ do
+ {
+ if (len < MAX_TOKEN_CHARS - 1)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ data++;
+ c = *data;
+ if ( c == '\n' )
+ com_lines++;
+ } while (c>32);
+
+ com_token[len] = 0;
+
+ *data_p = ( char * ) data;
+ return com_token;
+}
+
+
+#if 0
+// no longer used
+/*
+===============
+COM_ParseInfos
+===============
+*/
+int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ) {
+ char *token;
+ int count;
+ char key[MAX_TOKEN_CHARS];
+
+ count = 0;
+
+ while ( 1 ) {
+ token = COM_Parse( &buf );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( strcmp( token, "{" ) ) {
+ Com_Printf( "Missing { in info file\n" );
+ break;
+ }
+
+ if ( count == max ) {
+ Com_Printf( "Max infos exceeded\n" );
+ break;
+ }
+
+ infos[count][0] = 0;
+ while ( 1 ) {
+ token = COM_ParseExt( &buf, qtrue );
+ if ( !token[0] ) {
+ Com_Printf( "Unexpected end of info file\n" );
+ break;
+ }
+ if ( !strcmp( token, "}" ) ) {
+ break;
+ }
+ Q_strncpyz( key, token, sizeof( key ) );
+
+ token = COM_ParseExt( &buf, qfalse );
+ if ( !token[0] ) {
+ strcpy( token, "<NULL>" );
+ }
+ Info_SetValueForKey( infos[count], key, token );
+ }
+ count++;
+ }
+
+ return count;
+}
+#endif
+
+
+/*
+==================
+COM_MatchToken
+==================
+*/
+void COM_MatchToken( char **buf_p, char *match ) {
+ char *token;
+
+ token = COM_Parse( buf_p );
+ if ( strcmp( token, match ) ) {
+ Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match );
+ }
+}
+
+
+/*
+=================
+SkipBracedSection
+
+The next token should be an open brace.
+Skips until a matching close brace is found.
+Internal brace depths are properly skipped.
+=================
+*/
+void SkipBracedSection (char **program) {
+ char *token;
+ int depth;
+
+ depth = 0;
+ do {
+ token = COM_ParseExt( program, qtrue );
+ if( token[1] == 0 ) {
+ if( token[0] == '{' ) {
+ depth++;
+ }
+ else if( token[0] == '}' ) {
+ depth--;
+ }
+ }
+ } while( depth && *program );
+}
+
+/*
+=================
+SkipRestOfLine
+=================
+*/
+void SkipRestOfLine ( char **data ) {
+ char *p;
+ int c;
+
+ p = *data;
+ while ( (c = *p++) != 0 ) {
+ if ( c == '\n' ) {
+ com_lines++;
+ break;
+ }
+ }
+
+ *data = p;
+}
+
+
+void Parse1DMatrix (char **buf_p, int x, float *m) {
+ char *token;
+ int i;
+
+ COM_MatchToken( buf_p, "(" );
+
+ for (i = 0 ; i < x ; i++) {
+ token = COM_Parse(buf_p);
+ m[i] = atof(token);
+ }
+
+ COM_MatchToken( buf_p, ")" );
+}
+
+void Parse2DMatrix (char **buf_p, int y, int x, float *m) {
+ int i;
+
+ COM_MatchToken( buf_p, "(" );
+
+ for (i = 0 ; i < y ; i++) {
+ Parse1DMatrix (buf_p, x, m + i * x);
+ }
+
+ COM_MatchToken( buf_p, ")" );
+}
+
+void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m) {
+ int i;
+
+ COM_MatchToken( buf_p, "(" );
+
+ for (i = 0 ; i < z ; i++) {
+ Parse2DMatrix (buf_p, y, x, m + i * x*y);
+ }
+
+ COM_MatchToken( buf_p, ")" );
+}
+
+
+/*
+============================================================================
+
+ LIBRARY REPLACEMENT FUNCTIONS
+
+============================================================================
+*/
+
+int Q_isprint( int c )
+{
+ if ( c >= 0x20 && c <= 0x7E )
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_islower( int c )
+{
+ if (c >= 'a' && c <= 'z')
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_isupper( int c )
+{
+ if (c >= 'A' && c <= 'Z')
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_isalpha( int c )
+{
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_isdigit( int c )
+{
+ if ((c >= '0' && c <= '9'))
+ return ( 1 );
+ return ( 0 );
+}
+
+char* Q_strrchr( const char* string, int c )
+{
+ char cc = c;
+ char *s;
+ char *sp=(char *)0;
+
+ s = (char*)string;
+
+ while (*s)
+ {
+ if (*s == cc)
+ sp = s;
+ s++;
+ }
+ if (cc == 0)
+ sp = s;
+
+ return sp;
+}
+
+/*
+=============
+Q_strncpyz
+
+Safe strncpy that ensures a trailing zero
+=============
+*/
+void Q_strncpyz( char *dest, const char *src, int destsize ) {
+ // bk001129 - also NULL dest
+ if ( !dest ) {
+ Com_Error( ERR_FATAL, "Q_strncpyz: NULL dest" );
+ }
+ if ( !src ) {
+ Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" );
+ }
+ if ( destsize < 1 ) {
+ Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" );
+ }
+
+ strncpy( dest, src, destsize-1 );
+ dest[destsize-1] = 0;
+}
+
+int Q_stricmpn (const char *s1, const char *s2, int n) {
+ int c1, c2;
+
+ // bk001129 - moved in 1.17 fix not in id codebase
+ if ( s1 == NULL ) {
+ if ( s2 == NULL )
+ return 0;
+ else
+ return -1;
+ }
+ else if ( s2==NULL )
+ return 1;
+
+
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (!n--) {
+ return 0; // strings are equal until end point
+ }
+
+ if (c1 != c2) {
+ if (c1 >= 'a' && c1 <= 'z') {
+ c1 -= ('a' - 'A');
+ }
+ if (c2 >= 'a' && c2 <= 'z') {
+ c2 -= ('a' - 'A');
+ }
+ if (c1 != c2) {
+ return c1 < c2 ? -1 : 1;
+ }
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+int Q_strncmp (const char *s1, const char *s2, int n) {
+ int c1, c2;
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (!n--) {
+ return 0; // strings are equal until end point
+ }
+
+ if (c1 != c2) {
+ return c1 < c2 ? -1 : 1;
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+int Q_stricmp (const char *s1, const char *s2) {
+ return (s1 && s2) ? Q_stricmpn (s1, s2, 99999) : -1;
+}
+
+
+char *Q_strlwr( char *s1 ) {
+ char *s;
+
+ s = s1;
+ while ( *s ) {
+ *s = tolower(*s);
+ s++;
+ }
+ return s1;
+}
+
+char *Q_strupr( char *s1 ) {
+ char *s;
+
+ s = s1;
+ while ( *s ) {
+ *s = toupper(*s);
+ s++;
+ }
+ return s1;
+}
+
+
+// never goes past bounds or leaves without a terminating 0
+void Q_strcat( char *dest, int size, const char *src ) {
+ int l1;
+
+ l1 = strlen( dest );
+ if ( l1 >= size ) {
+ Com_Error( ERR_FATAL, "Q_strcat: already overflowed" );
+ }
+ Q_strncpyz( dest + l1, src, size - l1 );
+}
+
+
+int Q_PrintStrlen( const char *string ) {
+ int len;
+ const char *p;
+
+ if( !string ) {
+ return 0;
+ }
+
+ len = 0;
+ p = string;
+ while( *p ) {
+ if( Q_IsColorString( p ) ) {
+ p += 2;
+ continue;
+ }
+ p++;
+ len++;
+ }
+
+ return len;
+}
+
+
+char *Q_CleanStr( char *string ) {
+ char* d;
+ char* s;
+ int c;
+
+ s = string;
+ d = string;
+ while ((c = *s) != 0 ) {
+ if ( Q_IsColorString( s ) ) {
+ s++;
+ }
+ else if ( c >= 0x20 && c <= 0x7E ) {
+ *d++ = c;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return string;
+}
+
+
+void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) {
+ int len;
+ va_list argptr;
+ char bigbuffer[32000]; // big, but small enough to fit in PPC stack
+
+ va_start (argptr,fmt);
+ len = vsprintf (bigbuffer,fmt,argptr);
+ va_end (argptr);
+ if ( len >= sizeof( bigbuffer ) ) {
+ Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" );
+ }
+ if (len >= size) {
+ Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size);
+#ifdef _DEBUG
+ __asm {
+ int 3;
+ }
+#endif
+ }
+ Q_strncpyz (dest, bigbuffer, size );
+}
+
+
+/*
+============
+va
+
+does a varargs printf into a temp buffer, so I don't need to have
+varargs versions of all text functions.
+FIXME: make this buffer size safe someday
+============
+*/
+char * QDECL va( char *format, ... ) {
+ va_list argptr;
+ static char string[2][32000]; // in case va is called by nested functions
+ static int index = 0;
+ char *buf;
+
+ buf = string[index & 1];
+ index++;
+
+ va_start (argptr, format);
+ vsprintf (buf, format,argptr);
+ va_end (argptr);
+
+ return buf;
+}
+
+/*
+============
+Com_TruncateLongString
+
+Assumes buffer is atleast TRUNCATE_LENGTH big
+============
+*/
+void Com_TruncateLongString( char *buffer, const char *s )
+{
+ int length = strlen( s );
+
+ if( length <= TRUNCATE_LENGTH )
+ Q_strncpyz( buffer, s, TRUNCATE_LENGTH );
+ else
+ {
+ Q_strncpyz( buffer, s, ( TRUNCATE_LENGTH / 2 ) - 3 );
+ Q_strcat( buffer, TRUNCATE_LENGTH, " ... " );
+ Q_strcat( buffer, TRUNCATE_LENGTH, s + length - ( TRUNCATE_LENGTH / 2 ) + 3 );
+ }
+}
+
+/*
+=====================================================================
+
+ INFO STRINGS
+
+=====================================================================
+*/
+
+/*
+===============
+Info_ValueForKey
+
+Searches the string for the given
+key and returns the associated value, or an empty string.
+FIXME: overflow check?
+===============
+*/
+char *Info_ValueForKey( const char *s, const char *key ) {
+ char pkey[BIG_INFO_KEY];
+ static char value[2][BIG_INFO_VALUE]; // use two buffers so compares
+ // work without stomping on each other
+ static int valueindex = 0;
+ char *o;
+
+ if ( !s || !key ) {
+ return "";
+ }
+
+ if ( strlen( s ) >= BIG_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" );
+ }
+
+ valueindex ^= 1;
+ if (*s == '\\')
+ s++;
+ while (1)
+ {
+ o = pkey;
+ while (*s != '\\')
+ {
+ if (!*s)
+ return "";
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value[valueindex];
+
+ while (*s != '\\' && *s)
+ {
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ if (!Q_stricmp (key, pkey) )
+ return value[valueindex];
+
+ if (!*s)
+ break;
+ s++;
+ }
+
+ return "";
+}
+
+
+/*
+===================
+Info_NextPair
+
+Used to itterate through all the key/value pairs in an info string
+===================
+*/
+void Info_NextPair( const char **head, char *key, char *value ) {
+ char *o;
+ const char *s;
+
+ s = *head;
+
+ if ( *s == '\\' ) {
+ s++;
+ }
+ key[0] = 0;
+ value[0] = 0;
+
+ o = key;
+ while ( *s != '\\' ) {
+ if ( !*s ) {
+ *o = 0;
+ *head = s;
+ return;
+ }
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value;
+ while ( *s != '\\' && *s ) {
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ *head = s;
+}
+
+
+/*
+===================
+Info_RemoveKey
+===================
+*/
+void Info_RemoveKey( char *s, const char *key ) {
+ char *start;
+ char pkey[MAX_INFO_KEY];
+ char value[MAX_INFO_VALUE];
+ char *o;
+
+ if ( strlen( s ) >= MAX_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" );
+ }
+
+ if (strchr (key, '\\')) {
+ return;
+ }
+
+ while (1)
+ {
+ start = s;
+ if (*s == '\\')
+ s++;
+ o = pkey;
+ while (*s != '\\')
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value;
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ if (!strcmp (key, pkey) )
+ {
+ strcpy (start, s); // remove this part
+ return;
+ }
+
+ if (!*s)
+ return;
+ }
+
+}
+
+/*
+===================
+Info_RemoveKey_Big
+===================
+*/
+void Info_RemoveKey_Big( char *s, const char *key ) {
+ char *start;
+ char pkey[BIG_INFO_KEY];
+ char value[BIG_INFO_VALUE];
+ char *o;
+
+ if ( strlen( s ) >= BIG_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_RemoveKey_Big: oversize infostring" );
+ }
+
+ if (strchr (key, '\\')) {
+ return;
+ }
+
+ while (1)
+ {
+ start = s;
+ if (*s == '\\')
+ s++;
+ o = pkey;
+ while (*s != '\\')
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value;
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ if (!strcmp (key, pkey) )
+ {
+ strcpy (start, s); // remove this part
+ return;
+ }
+
+ if (!*s)
+ return;
+ }
+
+}
+
+
+
+
+/*
+==================
+Info_Validate
+
+Some characters are illegal in info strings because they
+can mess up the server's parsing
+==================
+*/
+qboolean Info_Validate( const char *s ) {
+ const char* ch = s;
+
+ while ( *ch != '\0' )
+ {
+ if( !Q_isprint( *ch ) )
+ return qfalse;
+
+ if( *ch == '\"' )
+ return qfalse;
+
+ if( *ch == ';' )
+ return qfalse;
+
+ ++ch;
+ }
+
+ return qtrue;
+}
+
+/*
+==================
+Info_SetValueForKey
+
+Changes or adds a key/value pair
+==================
+*/
+void Info_SetValueForKey( char *s, const char *key, const char *value ) {
+ char newi[MAX_INFO_STRING];
+ const char* blacklist = "\\;\"";
+
+ if ( strlen( s ) >= MAX_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
+ }
+
+ for(; *blacklist; ++blacklist)
+ {
+ if (strchr (key, *blacklist) || strchr (value, *blacklist))
+ {
+ Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value);
+ return;
+ }
+ }
+
+ Info_RemoveKey (s, key);
+ if (!value || !strlen(value))
+ return;
+
+ Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
+
+ if (strlen(newi) + strlen(s) >= MAX_INFO_STRING)
+ {
+ Com_Printf ("Info string length exceeded\n");
+ return;
+ }
+
+ strcat (newi, s);
+ strcpy (s, newi);
+}
+
+/*
+==================
+Info_SetValueForKey_Big
+
+Changes or adds a key/value pair
+==================
+*/
+void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) {
+ char newi[BIG_INFO_STRING];
+ const char* blacklist = "\\;\"";
+
+ if ( strlen( s ) >= BIG_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
+ }
+
+ for(; *blacklist; ++blacklist)
+ {
+ if (strchr (key, *blacklist) || strchr (value, *blacklist))
+ {
+ Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value);
+ return;
+ }
+ }
+
+ Info_RemoveKey_Big (s, key);
+ if (!value || !strlen(value))
+ return;
+
+ Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
+
+ if (strlen(newi) + strlen(s) >= BIG_INFO_STRING)
+ {
+ Com_Printf ("BIG Info string length exceeded\n");
+ return;
+ }
+
+ strcat (s, newi);
+}
+
+
+
+
+//====================================================================
+
+/*
+==================
+Com_CharIsOneOfCharset
+==================
+*/
+static qboolean Com_CharIsOneOfCharset( char c, char *set )
+{
+ int i;
+
+ for( i = 0; i < strlen( set ); i++ )
+ {
+ if( set[ i ] == c )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+==================
+Com_SkipCharset
+==================
+*/
+char *Com_SkipCharset( char *s, char *sep )
+{
+ char *p = s;
+
+ while( p )
+ {
+ if( Com_CharIsOneOfCharset( *p, sep ) )
+ p++;
+ else
+ break;
+ }
+
+ return p;
+}
+
+/*
+==================
+Com_SkipTokens
+==================
+*/
+char *Com_SkipTokens( char *s, int numTokens, char *sep )
+{
+ int sepCount = 0;
+ char *p = s;
+
+ while( sepCount < numTokens )
+ {
+ if( Com_CharIsOneOfCharset( *p++, sep ) )
+ {
+ sepCount++;
+ while( Com_CharIsOneOfCharset( *p, sep ) )
+ p++;
+ }
+ else if( *p == '\0' )
+ break;
+ }
+
+ if( sepCount == numTokens )
+ return p;
+ else
+ return s;
+}
diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h
new file mode 100644
index 0000000..b84e476
--- /dev/null
+++ b/src/qcommon/q_shared.h
@@ -0,0 +1,1342 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __Q_SHARED_H
+#define __Q_SHARED_H
+
+// q_shared.h -- included first by ALL program modules.
+// A user mod should never modify this file
+
+#define VERSION_NUMBER "1.1.0"
+#define Q3_VERSION "tremulous " VERSION_NUMBER
+#ifndef SVN_VERSION
+#define SVN_VERSION Q3_VERSION
+#endif
+#define CLIENT_WINDOW_TITLE "Tremulous " VERSION_NUMBER
+#define CLIENT_WINDOW_ICON "Tremulous"
+#define CONSOLE_WINDOW_TITLE "Tremulous " VERSION_NUMBER " console"
+#define CONSOLE_WINDOW_ICON "Tremulous console"
+
+#define MAX_TEAMNAME 32
+
+#ifdef _MSC_VER
+
+#pragma warning(disable : 4018) // signed/unsigned mismatch
+#pragma warning(disable : 4032)
+#pragma warning(disable : 4051)
+#pragma warning(disable : 4057) // slightly different base types
+#pragma warning(disable : 4100) // unreferenced formal parameter
+#pragma warning(disable : 4115)
+#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence
+#pragma warning(disable : 4127) // conditional expression is constant
+#pragma warning(disable : 4136)
+#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression
+//#pragma warning(disable : 4201)
+//#pragma warning(disable : 4214)
+#pragma warning(disable : 4244)
+#pragma warning(disable : 4142) // benign redefinition
+//#pragma warning(disable : 4305) // truncation from const double to float
+//#pragma warning(disable : 4310) // cast truncates constant value
+//#pragma warning(disable: 4505) // unreferenced local function has been removed
+#pragma warning(disable : 4514)
+#pragma warning(disable : 4702) // unreachable code
+#pragma warning(disable : 4711) // selected for automatic inline expansion
+#pragma warning(disable : 4220) // varargs matches remaining parameters
+//#pragma intrinsic( memset, memcpy )
+#endif
+
+//Ignore __attribute__ on non-gcc platforms
+#ifndef __GNUC__
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+#if (defined _MSC_VER)
+#define Q_EXPORT __declspec(dllexport)
+#elif (defined __SUNPRO_C)
+#define Q_EXPORT __global
+#elif ((__GNUC__ >= 3) && (!__EMX__) && (!sun))
+#define Q_EXPORT __attribute__((visibility("default")))
+#else
+#define Q_EXPORT
+#endif
+
+/**********************************************************************
+ VM Considerations
+
+ The VM can not use the standard system headers because we aren't really
+ using the compiler they were meant for. We use bg_lib.h which contains
+ prototypes for the functions we define for our own use in bg_lib.c.
+
+ When writing mods, please add needed headers HERE, do not start including
+ stuff like <stdio.h> in the various .c files that make up each of the VMs
+ since you will be including system headers files can will have issues.
+
+ Remember, if you use a C library function that is not defined in bg_lib.c,
+ you will have to add your own version for support in the VM.
+
+ **********************************************************************/
+
+#ifdef Q3_VM
+
+#include "../game/bg_lib.h"
+
+#else
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+#include <limits.h>
+
+#endif
+
+#include "q_platform.h"
+
+//=============================================================
+
+#ifdef Q3_VM
+ typedef int intptr_t;
+#else
+ #ifndef _MSC_VER
+ #include <stdint.h>
+ #else
+ #include <io.h>
+ typedef __int64 int64_t;
+ typedef __int32 int32_t;
+ typedef __int16 int16_t;
+ typedef __int8 int8_t;
+ typedef unsigned __int64 uint64_t;
+ typedef unsigned __int32 uint32_t;
+ typedef unsigned __int16 uint16_t;
+ typedef unsigned __int8 uint8_t;
+ #endif
+#endif
+
+typedef unsigned char byte;
+
+typedef enum {qfalse, qtrue} qboolean;
+
+typedef int qhandle_t;
+typedef int sfxHandle_t;
+typedef int fileHandle_t;
+typedef int clipHandle_t;
+
+#define PAD(x,y) (((x)+(y)-1) & ~((y)-1))
+
+#ifdef __GNUC__
+#define ALIGN(x) __attribute__((aligned(x)))
+#else
+#define ALIGN(x)
+#endif
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#define MAX_QINT 0x7fffffff
+#define MIN_QINT (-MAX_QINT-1)
+
+
+// angle indexes
+#define PITCH 0 // up / down
+#define YAW 1 // left / right
+#define ROLL 2 // fall over
+
+// the game guarantees that no string from the network will ever
+// exceed MAX_STRING_CHARS
+#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString
+#define MAX_STRING_TOKENS 1024 // max tokens resulting from Cmd_TokenizeString
+#define MAX_TOKEN_CHARS 1024 // max length of an individual token
+
+#define MAX_INFO_STRING 1024
+#define MAX_INFO_KEY 1024
+#define MAX_INFO_VALUE 1024
+
+#define BIG_INFO_STRING 8192 // used for system info key only
+#define BIG_INFO_KEY 8192
+#define BIG_INFO_VALUE 8192
+
+
+#define MAX_QPATH 64 // max length of a quake game pathname
+#ifdef PATH_MAX
+#define MAX_OSPATH PATH_MAX
+#else
+#define MAX_OSPATH 256 // max length of a filesystem pathname
+#endif
+
+#define MAX_NAME_LENGTH 32 // max length of a client name
+#define MAX_HOSTNAME_LENGTH 80 // max length of a host name
+
+#define MAX_SAY_TEXT 150
+
+// paramters for command buffer stuffing
+typedef enum {
+ EXEC_NOW, // don't return until completed, a VM should NEVER use this,
+ // because some commands might cause the VM to be unloaded...
+ EXEC_INSERT, // insert at current position, but don't run yet
+ EXEC_APPEND // add to end of the command buffer (normal case)
+} cbufExec_t;
+
+
+//
+// these aren't needed by any of the VMs. put in another header?
+//
+#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility
+
+
+// print levels from renderer (FIXME: set up for game / cgame?)
+typedef enum {
+ PRINT_ALL,
+ PRINT_DEVELOPER, // only print when "developer 1"
+ PRINT_WARNING,
+ PRINT_ERROR
+} printParm_t;
+
+
+#ifdef ERR_FATAL
+#undef ERR_FATAL // this is be defined in malloc.h
+#endif
+
+// parameters to the main Error routine
+typedef enum {
+ ERR_FATAL, // exit the entire game with a popup window
+ ERR_DROP, // print to console and disconnect from game
+ ERR_SERVERDISCONNECT, // don't kill server
+ ERR_DISCONNECT, // client disconnected from the server
+ ERR_NEED_CD // pop up the need-cd dialog
+} errorParm_t;
+
+
+// font rendering values used by ui and cgame
+
+#define PROP_GAP_WIDTH 3
+#define PROP_SPACE_WIDTH 8
+#define PROP_HEIGHT 27
+#define PROP_SMALL_SIZE_SCALE 0.75
+
+#define BLINK_DIVISOR 200
+#define PULSE_DIVISOR 75
+
+#define UI_LEFT 0x00000000 // default
+#define UI_CENTER 0x00000001
+#define UI_RIGHT 0x00000002
+#define UI_FORMATMASK 0x00000007
+#define UI_SMALLFONT 0x00000010
+#define UI_BIGFONT 0x00000020 // default
+#define UI_GIANTFONT 0x00000040
+#define UI_DROPSHADOW 0x00000800
+#define UI_BLINK 0x00001000
+#define UI_INVERSE 0x00002000
+#define UI_PULSE 0x00004000
+
+#if defined(_DEBUG) && !defined(BSPC)
+ #define HUNK_DEBUG
+#endif
+
+typedef enum {
+ h_high,
+ h_low,
+ h_dontcare
+} ha_pref;
+
+#ifdef HUNK_DEBUG
+#define Hunk_Alloc( size, preference ) Hunk_AllocDebug(size, preference, #size, __FILE__, __LINE__)
+void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line );
+#else
+void *Hunk_Alloc( int size, ha_pref preference );
+#endif
+
+#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(__APPLE__)
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371
+// custom Snd_Memset implementation for glibc memset bug workaround
+void Snd_Memset (void* dest, const int val, const size_t count);
+#else
+#define Snd_Memset Com_Memset
+#endif
+
+#define Com_Memset memset
+#define Com_Memcpy memcpy
+
+#define CIN_system 1
+#define CIN_loop 2
+#define CIN_hold 4
+#define CIN_silent 8
+#define CIN_shader 16
+
+/*
+==============================================================
+
+MATHLIB
+
+==============================================================
+*/
+
+
+typedef float vec_t;
+typedef vec_t vec2_t[2];
+typedef vec_t vec3_t[3];
+typedef vec_t vec4_t[4];
+typedef vec_t vec5_t[5];
+
+typedef int fixed4_t;
+typedef int fixed8_t;
+typedef int fixed16_t;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846f // matches value in gcc v2 math.h
+#endif
+
+#ifndef M_SQRT2
+#define M_SQRT2 1.414213562f
+#endif
+
+#ifndef M_ROOT3
+#define M_ROOT3 1.732050808f
+#endif
+
+#define NUMVERTEXNORMALS 162
+extern vec3_t bytedirs[NUMVERTEXNORMALS];
+
+// all drawing is done to a 640*480 virtual screen size
+// and will be automatically scaled to the real resolution
+#define SCREEN_WIDTH 640
+#define SCREEN_HEIGHT 480
+
+#define TINYCHAR_WIDTH (SMALLCHAR_WIDTH)
+#define TINYCHAR_HEIGHT (SMALLCHAR_HEIGHT/2)
+
+#define SMALLCHAR_WIDTH 8
+#define SMALLCHAR_HEIGHT 16
+
+#define BIGCHAR_WIDTH 16
+#define BIGCHAR_HEIGHT 16
+
+#define GIANTCHAR_WIDTH 32
+#define GIANTCHAR_HEIGHT 48
+
+extern vec4_t colorBlack;
+extern vec4_t colorRed;
+extern vec4_t colorGreen;
+extern vec4_t colorBlue;
+extern vec4_t colorYellow;
+extern vec4_t colorMagenta;
+extern vec4_t colorCyan;
+extern vec4_t colorWhite;
+extern vec4_t colorLtGrey;
+extern vec4_t colorMdGrey;
+extern vec4_t colorDkGrey;
+
+#define Q_COLOR_ESCAPE '^'
+#define Q_IsColorString(p) ( p && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE )
+
+#define COLOR_BLACK '0'
+#define COLOR_RED '1'
+#define COLOR_GREEN '2'
+#define COLOR_YELLOW '3'
+#define COLOR_BLUE '4'
+#define COLOR_CYAN '5'
+#define COLOR_MAGENTA '6'
+#define COLOR_WHITE '7'
+#define ColorIndex(c) ( ( (c) - '0' ) & 7 )
+
+#define S_COLOR_BLACK "^0"
+#define S_COLOR_RED "^1"
+#define S_COLOR_GREEN "^2"
+#define S_COLOR_YELLOW "^3"
+#define S_COLOR_BLUE "^4"
+#define S_COLOR_CYAN "^5"
+#define S_COLOR_MAGENTA "^6"
+#define S_COLOR_WHITE "^7"
+
+extern vec4_t g_color_table[8];
+
+#define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b
+#define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a
+
+#define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F )
+#define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI )
+
+struct cplane_s;
+
+extern vec3_t vec3_origin;
+extern vec3_t axisDefault[3];
+
+#define nanmask (255<<23)
+
+#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)
+
+#if idppc
+
+static ID_INLINE float Q_rsqrt( float number ) {
+ float x = 0.5f * number;
+ float y;
+#ifdef __GNUC__
+ asm("frsqrte %0,%1" : "=f" (y) : "f" (number));
+#else
+ y = __frsqrte( number );
+#endif
+ return y * (1.5f - (x * y * y));
+ }
+
+#ifdef __GNUC__
+static ID_INLINE float Q_fabs(float x) {
+ float abs_x;
+
+ asm("fabs %0,%1" : "=f" (abs_x) : "f" (x));
+ return abs_x;
+}
+#else
+#define Q_fabs __fabsf
+#endif
+
+#else
+float Q_fabs( float f );
+float Q_rsqrt( float f ); // reciprocal square root
+#endif
+
+#define SQRTFAST( x ) ( (x) * Q_rsqrt( x ) )
+
+signed char ClampChar( int i );
+signed short ClampShort( int i );
+
+// this isn't a real cheap function to call!
+int DirToByte( vec3_t dir );
+void ByteToDir( int b, vec3_t dir );
+
+#if 1
+
+#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])
+#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2])
+#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2])
+#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
+#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s))
+#define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s))
+#define VectorLerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\
+ (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\
+ (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2]))
+
+#else
+
+#define DotProduct(x,y) _DotProduct(x,y)
+#define VectorSubtract(a,b,c) _VectorSubtract(a,b,c)
+#define VectorAdd(a,b,c) _VectorAdd(a,b,c)
+#define VectorCopy(a,b) _VectorCopy(a,b)
+#define VectorScale(v, s, o) _VectorScale(v,s,o)
+#define VectorMA(v, s, b, o) _VectorMA(v,s,b,o)
+
+#endif
+
+#ifdef Q3_VM
+#ifdef VectorCopy
+#undef VectorCopy
+// this is a little hack to get more efficient copies in our interpreter
+typedef struct {
+ float v[3];
+} vec3struct_t;
+#define VectorCopy(a,b) (*(vec3struct_t *)b=*(vec3struct_t *)a)
+#endif
+#endif
+
+#define Vector2Set(v, x, y) ((v)[0]=(x), (v)[1]=(y))
+#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0)
+#define VectorNegate(a,b) ((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2])
+#define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z))
+#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3])
+#define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3])
+
+#define SnapVector(v) {v[0]=((int)(v[0]));v[1]=((int)(v[1]));v[2]=((int)(v[2]));}
+// just in case you do't want to use the macros
+vec_t _DotProduct( const vec3_t v1, const vec3_t v2 );
+void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out );
+void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out );
+void _VectorCopy( const vec3_t in, vec3_t out );
+void _VectorScale( const vec3_t in, float scale, vec3_t out );
+void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc );
+
+unsigned ColorBytes3 (float r, float g, float b);
+unsigned ColorBytes4 (float r, float g, float b, float a);
+
+float NormalizeColor( const vec3_t in, vec3_t out );
+
+float RadiusFromBounds( const vec3_t mins, const vec3_t maxs );
+void ClearBounds( vec3_t mins, vec3_t maxs );
+void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs );
+
+#if !defined( Q3_VM ) || ( defined( Q3_VM ) && defined( __Q3_VM_MATH ) )
+static ID_INLINE int VectorCompare( const vec3_t v1, const vec3_t v2 ) {
+ if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) {
+ return 0;
+ }
+ return 1;
+}
+
+static ID_INLINE int VectorCompareEpsilon(
+ const vec3_t v1, const vec3_t v2, float epsilon )
+{
+ vec3_t d;
+
+ VectorSubtract( v1, v2, d );
+ d[ 0 ] = fabs( d[ 0 ] );
+ d[ 1 ] = fabs( d[ 1 ] );
+ d[ 2 ] = fabs( d[ 2 ] );
+
+ if( d[ 0 ] > epsilon || d[ 1 ] > epsilon || d[ 2 ] > epsilon )
+ return 0;
+
+ return 1;
+}
+
+static ID_INLINE vec_t VectorLength( const vec3_t v ) {
+ return (vec_t)sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
+}
+
+static ID_INLINE vec_t VectorLengthSquared( const vec3_t v ) {
+ return (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
+}
+
+static ID_INLINE vec_t Distance( const vec3_t p1, const vec3_t p2 ) {
+ vec3_t v;
+
+ VectorSubtract (p2, p1, v);
+ return VectorLength( v );
+}
+
+static ID_INLINE vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ) {
+ vec3_t v;
+
+ VectorSubtract (p2, p1, v);
+ return v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+}
+
+// fast vector normalize routine that does not check to make sure
+// that length != 0, nor does it return length, uses rsqrt approximation
+static ID_INLINE void VectorNormalizeFast( vec3_t v )
+{
+ float ilength;
+
+ ilength = Q_rsqrt( DotProduct( v, v ) );
+
+ v[0] *= ilength;
+ v[1] *= ilength;
+ v[2] *= ilength;
+}
+
+static ID_INLINE void VectorInverse( vec3_t v ){
+ v[0] = -v[0];
+ v[1] = -v[1];
+ v[2] = -v[2];
+}
+
+static ID_INLINE void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ) {
+ cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
+ cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
+ cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
+}
+
+#else
+int VectorCompare( const vec3_t v1, const vec3_t v2 );
+
+vec_t VectorLength( const vec3_t v );
+
+vec_t VectorLengthSquared( const vec3_t v );
+
+vec_t Distance( const vec3_t p1, const vec3_t p2 );
+
+vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 );
+
+void VectorNormalizeFast( vec3_t v );
+
+void VectorInverse( vec3_t v );
+
+void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross );
+
+#endif
+
+vec_t VectorNormalize (vec3_t v); // returns vector length
+vec_t VectorNormalize2( const vec3_t v, vec3_t out );
+void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out );
+void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out );
+int Q_log2(int val);
+
+float Q_acos(float c);
+
+int Q_rand( int *seed );
+float Q_random( int *seed );
+float Q_crandom( int *seed );
+
+#define random() ((rand () & 0x7fff) / ((float)0x7fff))
+#define crandom() (2.0 * (random() - 0.5))
+
+void vectoangles( const vec3_t value1, vec3_t angles);
+void AnglesToAxis( const vec3_t angles, vec3_t axis[3] );
+void AxisToAngles( vec3_t axis[3], vec3_t angles );
+
+void AxisClear( vec3_t axis[3] );
+void AxisCopy( vec3_t in[3], vec3_t out[3] );
+
+void SetPlaneSignbits( struct cplane_s *out );
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane);
+
+float AngleMod(float a);
+float LerpAngle (float from, float to, float frac);
+float AngleSubtract( float a1, float a2 );
+void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 );
+
+float AngleNormalize360 ( float angle );
+float AngleNormalize180 ( float angle );
+float AngleDelta ( float angle1, float angle2 );
+
+qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c );
+void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal );
+void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees );
+void RotateAroundDirection( vec3_t axis[3], vec_t angle );
+void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up );
+// perpendicular vector could be replaced by this
+
+//int PlaneTypeForNormal (vec3_t normal);
+
+void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]);
+void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out );
+void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
+void PerpendicularVector( vec3_t dst, const vec3_t src );
+int Q_isnan( float x );
+
+void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1,
+ const vec3_t p2, vec3_t up );
+void ProjectPointOntoVector( vec3_t point, vec3_t vStart,
+ vec3_t vEnd, vec3_t vProj );
+float VectorDistance( vec3_t v1, vec3_t v2 );
+
+float pointToLineDistance( const vec3_t point, const vec3_t p1, const vec3_t p2 );
+float VectorMinComponent( vec3_t v );
+float VectorMaxComponent( vec3_t v );
+
+vec_t DistanceBetweenLineSegmentsSquared(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t );
+vec_t DistanceBetweenLineSegments(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t );
+
+#ifndef MAX
+#define MAX(x,y) ((x)>(y)?(x):(y))
+#endif
+
+#ifndef MIN
+#define MIN(x,y) ((x)<(y)?(x):(y))
+#endif
+
+//=============================================
+
+float Com_Clamp( float min, float max, float value );
+
+char *COM_SkipPath( char *pathname );
+void COM_StripExtension(const char *in, char *out, int destsize);
+void COM_DefaultExtension( char *path, int maxSize, const char *extension );
+
+void COM_BeginParseSession( const char *name );
+int COM_GetCurrentParseLine( void );
+char *COM_Parse( char **data_p );
+char *COM_ParseExt( char **data_p, qboolean allowLineBreak );
+int COM_Compress( char *data_p );
+void COM_ParseError( char *format, ... ) __attribute__ ((format (printf, 1, 2)));
+void COM_ParseWarning( char *format, ... ) __attribute__ ((format (printf, 1, 2)));
+//int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] );
+
+#define MAX_TOKENLENGTH 1024
+
+#ifndef TT_STRING
+//token types
+#define TT_STRING 1 // string
+#define TT_LITERAL 2 // literal
+#define TT_NUMBER 3 // number
+#define TT_NAME 4 // name
+#define TT_PUNCTUATION 5 // punctuation
+#endif
+
+typedef struct pc_token_s
+{
+ int type;
+ int subtype;
+ int intvalue;
+ float floatvalue;
+ char string[MAX_TOKENLENGTH];
+} pc_token_t;
+
+// data is an in/out parm, returns a parsed out token
+
+void COM_MatchToken( char**buf_p, char *match );
+
+void SkipBracedSection (char **program);
+void SkipRestOfLine ( char **data );
+
+void Parse1DMatrix (char **buf_p, int x, float *m);
+void Parse2DMatrix (char **buf_p, int y, int x, float *m);
+void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m);
+
+void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
+
+char *Com_SkipTokens( char *s, int numTokens, char *sep );
+char *Com_SkipCharset( char *s, char *sep );
+
+void Com_RandomBytes( byte *string, int len );
+
+// mode parm for FS_FOpenFile
+typedef enum {
+ FS_READ,
+ FS_WRITE,
+ FS_APPEND,
+ FS_APPEND_SYNC
+} fsMode_t;
+
+typedef enum {
+ FS_SEEK_CUR,
+ FS_SEEK_END,
+ FS_SEEK_SET
+} fsOrigin_t;
+
+//=============================================
+
+int Q_isprint( int c );
+int Q_islower( int c );
+int Q_isupper( int c );
+int Q_isalpha( int c );
+int Q_isdigit( int c );
+
+// portable case insensitive compare
+int Q_stricmp (const char *s1, const char *s2);
+int Q_strncmp (const char *s1, const char *s2, int n);
+int Q_stricmpn (const char *s1, const char *s2, int n);
+char *Q_strlwr( char *s1 );
+char *Q_strupr( char *s1 );
+char *Q_strrchr( const char* string, int c );
+
+// buffer size safe library replacements
+void Q_strncpyz( char *dest, const char *src, int destsize );
+void Q_strcat( char *dest, int size, const char *src );
+
+// strlen that discounts Quake color sequences
+int Q_PrintStrlen( const char *string );
+// removes color sequences from string
+char *Q_CleanStr( char *string );
+
+//=============================================
+
+// 64-bit integers for global rankings interface
+// implemented as a struct for qvm compatibility
+typedef struct
+{
+ byte b0;
+ byte b1;
+ byte b2;
+ byte b3;
+ byte b4;
+ byte b5;
+ byte b6;
+ byte b7;
+} qint64;
+
+//=============================================
+/*
+short BigShort(short l);
+short LittleShort(short l);
+int BigLong (int l);
+int LittleLong (int l);
+qint64 BigLong64 (qint64 l);
+qint64 LittleLong64 (qint64 l);
+float BigFloat (const float *l);
+float LittleFloat (const float *l);
+
+void Swap_Init (void);
+*/
+char * QDECL va(char *format, ...) __attribute__ ((format (printf, 1, 2)));
+
+#define TRUNCATE_LENGTH 64
+void Com_TruncateLongString( char *buffer, const char *s );
+
+//=============================================
+
+//
+// key / value info strings
+//
+char *Info_ValueForKey( const char *s, const char *key );
+void Info_RemoveKey( char *s, const char *key );
+void Info_RemoveKey_big( char *s, const char *key );
+void Info_SetValueForKey( char *s, const char *key, const char *value );
+void Info_SetValueForKey_Big( char *s, const char *key, const char *value );
+qboolean Info_Validate( const char *s );
+void Info_NextPair( const char **s, char *key, char *value );
+
+// this is only here so the functions in q_shared.c and bg_*.c can link
+void QDECL Com_Error( int level, const char *error, ... ) __attribute__ ((format (printf, 2, 3)));
+void QDECL Com_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2)));
+
+
+/*
+==========================================================
+
+CVARS (console variables)
+
+Many variables can be used for cheating purposes, so when
+cheats is zero, force all unspecified variables to their
+default values.
+==========================================================
+*/
+
+#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc
+ // used for system variables, not for player
+ // specific configurations
+#define CVAR_USERINFO 2 // sent to server on connect or change
+#define CVAR_SERVERINFO 4 // sent in response to front end requests
+#define CVAR_SYSTEMINFO 8 // these cvars will be duplicated on all clients
+#define CVAR_INIT 16 // don't allow change from console at all,
+ // but can be set from the command line
+#define CVAR_LATCH 32 // will only change when C code next does
+ // a Cvar_Get(), so it can't be changed
+ // without proper initialization. modified
+ // will be set, even though the value hasn't
+ // changed yet
+#define CVAR_ROM 64 // display only, cannot be set by user at all
+#define CVAR_USER_CREATED 128 // created by a set command
+#define CVAR_TEMP 256 // can be set even when cheats are disabled, but is not archived
+#define CVAR_CHEAT 512 // can not be changed if cheats are disabled
+#define CVAR_NORESTART 1024 // do not clear when a cvar_restart is issued
+
+#define CVAR_SERVER_CREATED 2048 // cvar was created by a server the client connected to.
+#define CVAR_NONEXISTENT 0xFFFFFFFF // Cvar doesn't exist.
+
+// nothing outside the Cvar_*() functions should modify these fields!
+typedef struct cvar_s {
+ char *name;
+ char *string;
+ char *resetString; // cvar_restart will reset to this value
+ char *latchedString; // for CVAR_LATCH vars
+ int flags;
+ qboolean modified; // set each time the cvar is changed
+ int modificationCount; // incremented each time the cvar is changed
+ float value; // atof( string )
+ int integer; // atoi( string )
+ struct cvar_s *next;
+ struct cvar_s *hashNext;
+} cvar_t;
+
+#define MAX_CVAR_VALUE_STRING 256
+
+typedef int cvarHandle_t;
+
+// the modules that run in the virtual machine can't access the cvar_t directly,
+// so they must ask for structured updates
+typedef struct {
+ cvarHandle_t handle;
+ int modificationCount;
+ float value;
+ int integer;
+ char string[MAX_CVAR_VALUE_STRING];
+} vmCvar_t;
+
+/*
+==============================================================
+
+COLLISION DETECTION
+
+==============================================================
+*/
+
+#include "surfaceflags.h" // shared with the q3map utility
+
+// plane types are used to speed some tests
+// 0-2 are axial planes
+#define PLANE_X 0
+#define PLANE_Y 1
+#define PLANE_Z 2
+#define PLANE_NON_AXIAL 3
+
+
+/*
+=================
+PlaneTypeForNormal
+=================
+*/
+
+#define PlaneTypeForNormal(x) (x[0] == 1.0 ? PLANE_X : (x[1] == 1.0 ? PLANE_Y : (x[2] == 1.0 ? PLANE_Z : PLANE_NON_AXIAL) ) )
+
+// plane_t structure
+// !!! if this is changed, it must be changed in asm code too !!!
+typedef struct cplane_s {
+ vec3_t normal;
+ float dist;
+ byte type; // for fast side tests: 0,1,2 = axial, 3 = nonaxial
+ byte signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision
+ byte pad[2];
+} cplane_t;
+
+typedef enum {
+ TT_NONE,
+
+ TT_AABB,
+ TT_CAPSULE,
+ TT_BISPHERE,
+
+ TT_NUM_TRACE_TYPES
+} traceType_t;
+
+// a trace is returned when a box is swept through the world
+typedef struct {
+ qboolean allsolid; // if true, plane is not valid
+ qboolean startsolid; // if true, the initial point was in a solid area
+ float fraction; // time completed, 1.0 = didn't hit anything
+ vec3_t endpos; // final position
+ cplane_t plane; // surface normal at impact, transformed to world space
+ int surfaceFlags; // surface hit
+ int contents; // contents on other side of surface hit
+ int entityNum; // entity the contacted sirface is a part of
+ float lateralFraction; // fraction of collision tangetially to the trace direction
+} trace_t;
+
+// trace->entityNum can also be 0 to (MAX_GENTITIES-1)
+// or ENTITYNUM_NONE, ENTITYNUM_WORLD
+
+
+// markfragments are returned by CM_MarkFragments()
+typedef struct {
+ int firstPoint;
+ int numPoints;
+} markFragment_t;
+
+
+
+typedef struct {
+ vec3_t origin;
+ vec3_t axis[3];
+} orientation_t;
+
+//=====================================================================
+
+
+// in order from highest priority to lowest
+// if none of the catchers are active, bound key strings will be executed
+#define KEYCATCH_CONSOLE 0x0001
+#define KEYCATCH_UI 0x0002
+#define KEYCATCH_MESSAGE 0x0004
+#define KEYCATCH_CGAME 0x0008
+
+
+// sound channels
+// channel 0 never willingly overrides
+// other channels will allways override a playing sound on that channel
+typedef enum {
+ CHAN_AUTO,
+ CHAN_LOCAL, // menu sounds, etc
+ CHAN_WEAPON,
+ CHAN_VOICE,
+ CHAN_ITEM,
+ CHAN_BODY,
+ CHAN_LOCAL_SOUND, // chat messages, etc
+ CHAN_ANNOUNCER // announcer voices, etc
+} soundChannel_t;
+
+
+/*
+========================================================================
+
+ ELEMENTS COMMUNICATED ACROSS THE NET
+
+========================================================================
+*/
+
+#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535)
+#define SHORT2ANGLE(x) ((x)*(360.0/65536))
+
+#define SNAPFLAG_RATE_DELAYED 1
+#define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies
+#define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected
+
+//
+// per-level limits
+//
+#define MAX_CLIENTS 64 // absolute limit
+#define MAX_LOCATIONS 64
+
+#define GENTITYNUM_BITS 10 // don't need to send any more
+#define MAX_GENTITIES (1<<GENTITYNUM_BITS)
+
+// entitynums are communicated with GENTITY_BITS, so any reserved
+// values that are going to be communcated over the net need to
+// also be in this range
+#define ENTITYNUM_NONE (MAX_GENTITIES-1)
+#define ENTITYNUM_WORLD (MAX_GENTITIES-2)
+#define ENTITYNUM_MAX_NORMAL (MAX_GENTITIES-2)
+
+
+#define MAX_MODELS 256 // these are sent over the net as 8 bits
+#define MAX_SOUNDS 256 // so they cannot be blindly increased
+#define MAX_GAME_SHADERS 64
+#define MAX_GAME_PARTICLE_SYSTEMS 64
+
+
+#define MAX_CONFIGSTRINGS 1024
+
+// these are the only configstrings that the system reserves, all the
+// other ones are strictly for servergame to clientgame communication
+#define CS_SERVERINFO 0 // an info string with all the serverinfo cvars
+#define CS_SYSTEMINFO 1 // an info string for server system to client system configuration (timescale, etc)
+
+#define RESERVED_CONFIGSTRINGS 2 // game can't modify below this, only the system can
+
+#define MAX_GAMESTATE_CHARS 16000
+typedef struct {
+ int stringOffsets[MAX_CONFIGSTRINGS];
+ char stringData[MAX_GAMESTATE_CHARS];
+ int dataCount;
+} gameState_t;
+
+//=========================================================
+
+// bit field limits
+#define MAX_STATS 16
+#define MAX_PERSISTANT 16
+#define MAX_MISC 16
+#define MAX_WEAPONS 16
+
+#define MAX_PS_EVENTS 2
+
+#define PS_PMOVEFRAMECOUNTBITS 6
+
+// playerState_t is the information needed by both the client and server
+// to predict player motion and actions
+// nothing outside of pmove should modify these, or some degree of prediction error
+// will occur
+
+// you can't add anything to this without modifying the code in msg.c
+
+// playerState_t is a full superset of entityState_t as it is used by players,
+// so if a playerState_t is transmitted, the entityState_t can be fully derived
+// from it.
+typedef struct playerState_s {
+ int commandTime; // cmd->serverTime of last executed command
+ int pm_type;
+ int bobCycle; // for view bobbing and footstep generation
+ int pm_flags; // ducked, jump_held, etc
+ int pm_time;
+
+ vec3_t origin;
+ vec3_t velocity;
+ int weaponTime;
+ int gravity;
+ int speed;
+ int delta_angles[3]; // add to command angles to get view direction
+ // changed by spawns, rotating objects, and teleporters
+
+ int groundEntityNum;// ENTITYNUM_NONE = in air
+
+ int legsTimer; // don't change low priority animations until this runs out
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+
+ int torsoTimer; // don't change low priority animations until this runs out
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+
+ int tauntTimer; // don't allow another taunt until this runs out
+
+ int weaponAnim; // mask off ANIM_TOGGLEBIT
+
+ int movementDir; // a number 0 to 7 that represents the relative angle
+ // of movement to the view angle (axial and diagonals)
+ // when at rest, the value will remain unchanged
+ // used to twist the legs during strafing
+
+ vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL
+
+ int eFlags; // copied to entityState_t->eFlags
+
+ int eventSequence; // pmove generated events
+ int events[MAX_PS_EVENTS];
+ int eventParms[MAX_PS_EVENTS];
+
+ int externalEvent; // events set on player from another source
+ int externalEventParm;
+ int externalEventTime;
+
+ int clientNum; // ranges from 0 to MAX_CLIENTS-1
+ int weapon; // copied to entityState_t->weapon
+ int weaponstate;
+
+ vec3_t viewangles; // for fixed views
+ int viewheight;
+
+ // damage feedback
+ int damageEvent; // when it changes, latch the other parms
+ int damageYaw;
+ int damagePitch;
+ int damageCount;
+
+ int stats[MAX_STATS];
+ int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death
+ int misc[MAX_MISC]; // misc data
+ int ammo; // ammo held
+ int clips; // clips held
+
+ int generic1;
+ int loopSound;
+ int otherEntityNum;
+
+ // not communicated over the net at all
+ int ping; // server to game info for scoreboard
+ int pmove_framecount;
+ int jumppad_frame;
+ int entityEventSequence;
+} playerState_t;
+
+
+//====================================================================
+
+
+//
+// usercmd_t->button bits, many of which are generated by the client system,
+// so they aren't game/cgame only definitions
+//
+#define BUTTON_ATTACK 1
+#define BUTTON_TALK 2 // displays talk balloon and disables actions
+#define BUTTON_USE_HOLDABLE 4
+#define BUTTON_GESTURE 8
+#define BUTTON_WALKING 16 // walking can't just be infered from MOVE_RUN
+ // because a key pressed late in the frame will
+ // only generate a small move value for that frame
+ // walking will use different animations and
+ // won't generate footsteps
+#define BUTTON_ATTACK2 32
+#define BUTTON_NEGATIVE 64
+
+#define BUTTON_GETFLAG 128
+#define BUTTON_GUARDBASE 256
+#define BUTTON_PATROL 512
+#define BUTTON_FOLLOWME 1024
+
+#define BUTTON_ANY 2048 // any key whatsoever
+
+#define MOVE_RUN 120 // if forwardmove or rightmove are >= MOVE_RUN,
+ // then BUTTON_WALKING should be set
+
+// usercmd_t is sent to the server each client frame
+typedef struct usercmd_s {
+ int serverTime;
+ int angles[3];
+ int buttons;
+ byte weapon; // weapon
+ signed char forwardmove, rightmove, upmove;
+} usercmd_t;
+
+//===================================================================
+
+// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number
+#define SOLID_BMODEL 0xffffff
+
+typedef enum {
+ TR_STATIONARY,
+ TR_INTERPOLATE, // non-parametric, but interpolate between snapshots
+ TR_LINEAR,
+ TR_LINEAR_STOP,
+ TR_SINE, // value = base + sin( time / duration ) * delta
+ TR_GRAVITY,
+ TR_BUOYANCY //TA: what the hell is this doing in here anyway?
+} trType_t;
+
+typedef struct {
+ trType_t trType;
+ int trTime;
+ int trDuration; // if non 0, trTime + trDuration = stop time
+ vec3_t trBase;
+ vec3_t trDelta; // velocity, etc
+} trajectory_t;
+
+// entityState_t is the information conveyed from the server
+// in an update message about entities that the client will
+// need to render in some way
+// Different eTypes may use the information in different ways
+// The messages are delta compressed, so it doesn't really matter if
+// the structure size is fairly large
+
+typedef struct entityState_s {
+ int number; // entity index
+ int eType; // entityType_t
+ int eFlags;
+
+ trajectory_t pos; // for calculating position
+ trajectory_t apos; // for calculating angles
+
+ int time;
+ int time2;
+
+ vec3_t origin;
+ vec3_t origin2;
+
+ vec3_t angles;
+ vec3_t angles2;
+
+ int otherEntityNum; // shotgun sources, etc
+ int otherEntityNum2;
+
+ int groundEntityNum; // -1 = in air
+
+ int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24)
+ int loopSound; // constantly loop this sound
+
+ int modelindex;
+ int modelindex2;
+ int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses
+ int frame;
+
+ int solid; // for client side prediction, trap_linkentity sets this properly
+
+ int event; // impulse events -- muzzle flashes, footsteps, etc
+ int eventParm;
+
+ // for players
+ int misc; // bit flags
+ int weapon; // determines weapon and flash model, etc
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+ int weaponAnim; // mask off ANIM_TOGGLEBIT
+
+ int generic1;
+} entityState_t;
+
+typedef enum {
+ CA_UNINITIALIZED,
+ CA_DISCONNECTED, // not talking to a server
+ CA_AUTHORIZING, // not used any more, was checking cd key
+ CA_CONNECTING, // sending request packets to the server
+ CA_CHALLENGING, // sending challenge packets to the server
+ CA_CONNECTED, // netchan_t established, getting gamestate
+ CA_LOADING, // only during cgame initialization, never during main loop
+ CA_PRIMED, // got gamestate, waiting for first frame
+ CA_ACTIVE, // game views should be displayed
+ CA_CINEMATIC // playing a cinematic or a static pic, not connected to a server
+} connstate_t;
+
+// font support
+
+#define GLYPH_START 0
+#define GLYPH_END 255
+#define GLYPH_CHARSTART 32
+#define GLYPH_CHAREND 127
+#define GLYPHS_PER_FONT GLYPH_END - GLYPH_START + 1
+typedef struct {
+ int height; // number of scan lines
+ int top; // top of glyph in buffer
+ int bottom; // bottom of glyph in buffer
+ int pitch; // width for copying
+ int xSkip; // x adjustment
+ int imageWidth; // width of actual image
+ int imageHeight; // height of actual image
+ float s; // x offset in image where glyph starts
+ float t; // y offset in image where glyph starts
+ float s2;
+ float t2;
+ qhandle_t glyph; // handle to the shader with the glyph
+ char shaderName[32];
+} glyphInfo_t;
+
+typedef struct {
+ glyphInfo_t glyphs [GLYPHS_PER_FONT];
+ float glyphScale;
+ char name[MAX_QPATH];
+} fontInfo_t;
+
+#define Square(x) ((x)*(x))
+
+// real time
+//=============================================
+
+
+typedef struct qtime_s {
+ int tm_sec; /* seconds after the minute - [0,59] */
+ int tm_min; /* minutes after the hour - [0,59] */
+ int tm_hour; /* hours since midnight - [0,23] */
+ int tm_mday; /* day of the month - [1,31] */
+ int tm_mon; /* months since January - [0,11] */
+ int tm_year; /* years since 1900 */
+ int tm_wday; /* days since Sunday - [0,6] */
+ int tm_yday; /* days since January 1 - [0,365] */
+ int tm_isdst; /* daylight savings time flag */
+} qtime_t;
+
+
+// server browser sources
+// TTimo: AS_MPLAYER is no longer used
+#define AS_GLOBAL 0
+#define AS_MPLAYER 1
+#define AS_LOCAL 2
+#define AS_FAVORITES 3
+
+
+// cinematic states
+typedef enum {
+ FMV_IDLE,
+ FMV_PLAY, // play
+ FMV_EOF, // all other conditions, i.e. stop/EOF/abort
+ FMV_ID_BLT,
+ FMV_ID_IDLE,
+ FMV_LOOPED,
+ FMV_ID_WAIT
+} e_status;
+
+typedef enum _flag_status {
+ FLAG_ATBASE = 0,
+ FLAG_TAKEN, // CTF
+ FLAG_TAKEN_RED, // One Flag CTF
+ FLAG_TAKEN_BLUE, // One Flag CTF
+ FLAG_DROPPED
+} flagStatus_t;
+
+typedef enum {
+ DS_NONE,
+
+ DS_PLAYBACK,
+ DS_RECORDING,
+
+ DS_NUM_DEMO_STATES
+} demoState_t;
+
+
+#define MAX_GLOBAL_SERVERS 4096
+#define MAX_OTHER_SERVERS 128
+#define MAX_PINGREQUESTS 32
+#define MAX_SERVERSTATUSREQUESTS 16
+
+#define SAY_ALL 0
+#define SAY_TEAM 1
+#define SAY_TELL 2
+#define SAY_ACTION 3
+#define SAY_ACTION_T 4
+#define SAY_ADMINS 5
+#define SAY_HADMINS 6
+
+#endif // __Q_SHARED_H
diff --git a/src/qcommon/qfiles.h b/src/qcommon/qfiles.h
new file mode 100644
index 0000000..7e901b7
--- /dev/null
+++ b/src/qcommon/qfiles.h
@@ -0,0 +1,626 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#ifndef __QFILES_H__
+#define __QFILES_H__
+
+//
+// qfiles.h: quake file formats
+// This file must be identical in the quake and utils directories
+//
+
+//Ignore __attribute__ on non-gcc platforms
+#ifndef __GNUC__
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+// surface geometry should not exceed these limits
+#define SHADER_MAX_VERTEXES 1000
+#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES)
+
+
+// the maximum size of game relative pathnames
+#define MAX_QPATH 64
+
+/*
+========================================================================
+
+QVM files
+
+========================================================================
+*/
+
+#define VM_MAGIC 0x12721444
+#define VM_MAGIC_VER2 0x12721445
+typedef struct {
+ int vmMagic;
+
+ int instructionCount;
+
+ int codeOffset;
+ int codeLength;
+
+ int dataOffset;
+ int dataLength;
+ int litLength; // ( dataLength - litLength ) should be byteswapped on load
+ int bssLength; // zero filled memory appended to datalength
+
+ //!!! below here is VM_MAGIC_VER2 !!!
+ int jtrgLength; // number of jump table targets
+} vmHeader_t;
+
+
+/*
+========================================================================
+
+PCX files are used for 8 bit images
+
+========================================================================
+*/
+
+typedef struct {
+ char manufacturer;
+ char version;
+ char encoding;
+ char bits_per_pixel;
+ unsigned short xmin,ymin,xmax,ymax;
+ unsigned short hres,vres;
+ unsigned char palette[48];
+ char reserved;
+ char color_planes;
+ unsigned short bytes_per_line;
+ unsigned short palette_type;
+ char filler[58];
+ unsigned char data; // unbounded
+} pcx_t;
+
+
+/*
+========================================================================
+
+TGA files are used for 24/32 bit images
+
+========================================================================
+*/
+
+typedef struct _TargaHeader {
+ unsigned char id_length, colormap_type, image_type;
+ unsigned short colormap_index, colormap_length;
+ unsigned char colormap_size;
+ unsigned short x_origin, y_origin, width, height;
+ unsigned char pixel_size, attributes;
+} TargaHeader;
+
+
+
+/*
+========================================================================
+
+.MD3 triangle model file format
+
+========================================================================
+*/
+
+#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I')
+#define MD3_VERSION 15
+
+// limits
+#define MD3_MAX_LODS 3
+#define MD3_MAX_TRIANGLES 8192 // per surface
+#define MD3_MAX_VERTS 4096 // per surface
+#define MD3_MAX_SHADERS 256 // per surface
+#define MD3_MAX_FRAMES 1024 // per model
+#define MD3_MAX_SURFACES 32 // per model
+#define MD3_MAX_TAGS 16 // per frame
+
+// vertex scales
+#define MD3_XYZ_SCALE (1.0/64)
+
+typedef struct md3Frame_s {
+ vec3_t bounds[2];
+ vec3_t localOrigin;
+ float radius;
+ char name[16];
+} md3Frame_t;
+
+typedef struct md3Tag_s {
+ char name[MAX_QPATH]; // tag name
+ vec3_t origin;
+ vec3_t axis[3];
+} md3Tag_t;
+
+/*
+** md3Surface_t
+**
+** CHUNK SIZE
+** header sizeof( md3Surface_t )
+** shaders sizeof( md3Shader_t ) * numShaders
+** triangles[0] sizeof( md3Triangle_t ) * numTriangles
+** st sizeof( md3St_t ) * numVerts
+** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames
+*/
+typedef struct {
+ int ident; //
+
+ char name[MAX_QPATH]; // polyset name
+
+ int flags;
+ int numFrames; // all surfaces in a model should have the same
+
+ int numShaders; // all surfaces in a model should have the same
+ int numVerts;
+
+ int numTriangles;
+ int ofsTriangles;
+
+ int ofsShaders; // offset from start of md3Surface_t
+ int ofsSt; // texture coords are common for all frames
+ int ofsXyzNormals; // numVerts * numFrames
+
+ int ofsEnd; // next surface follows
+} md3Surface_t;
+
+typedef struct {
+ char name[MAX_QPATH];
+ int shaderIndex; // for in-game use
+} md3Shader_t;
+
+typedef struct {
+ int indexes[3];
+} md3Triangle_t;
+
+typedef struct {
+ float st[2];
+} md3St_t;
+
+typedef struct {
+ short xyz[3];
+ short normal;
+} md3XyzNormal_t;
+
+typedef struct {
+ int ident;
+ int version;
+
+ char name[MAX_QPATH]; // model name
+
+ int flags;
+
+ int numFrames;
+ int numTags;
+ int numSurfaces;
+
+ int numSkins;
+
+ int ofsFrames; // offset for first frame
+ int ofsTags; // numFrames * numTags
+ int ofsSurfaces; // first surface, others follow
+
+ int ofsEnd; // end of file
+} md3Header_t;
+
+/*
+==============================================================================
+
+MD4 file format
+
+==============================================================================
+*/
+
+#define MD4_IDENT (('4'<<24)+('P'<<16)+('D'<<8)+'I')
+#define MD4_VERSION 1
+#define MD4_MAX_BONES 128
+
+typedef struct {
+ int boneIndex; // these are indexes into the boneReferences,
+ float boneWeight; // not the global per-frame bone list
+ vec3_t offset;
+} md4Weight_t;
+
+typedef struct {
+ vec3_t normal;
+ vec2_t texCoords;
+ int numWeights;
+ md4Weight_t weights[1]; // variable sized
+} md4Vertex_t;
+
+typedef struct {
+ int indexes[3];
+} md4Triangle_t;
+
+typedef struct {
+ int ident;
+
+ char name[MAX_QPATH]; // polyset name
+ char shader[MAX_QPATH];
+ int shaderIndex; // for in-game use
+
+ int ofsHeader; // this will be a negative number
+
+ int numVerts;
+ int ofsVerts;
+
+ int numTriangles;
+ int ofsTriangles;
+
+ // Bone references are a set of ints representing all the bones
+ // present in any vertex weights for this surface. This is
+ // needed because a model may have surfaces that need to be
+ // drawn at different sort times, and we don't want to have
+ // to re-interpolate all the bones for each surface.
+ int numBoneReferences;
+ int ofsBoneReferences;
+
+ int ofsEnd; // next surface follows
+} md4Surface_t;
+
+typedef struct {
+ float matrix[3][4];
+} md4Bone_t;
+
+typedef struct {
+ vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
+ vec3_t localOrigin; // midpoint of bounds, used for sphere cull
+ float radius; // dist from localOrigin to corner
+ md4Bone_t bones[1]; // [numBones]
+} md4Frame_t;
+
+typedef struct {
+ int numSurfaces;
+ int ofsSurfaces; // first surface, others follow
+ int ofsEnd; // next lod follows
+} md4LOD_t;
+
+typedef struct {
+ int ident;
+ int version;
+
+ char name[MAX_QPATH]; // model name
+
+ // frames and bones are shared by all levels of detail
+ int numFrames;
+ int numBones;
+ int ofsBoneNames; // char name[ MAX_QPATH ]
+ int ofsFrames; // md4Frame_t[numFrames]
+
+ // each level of detail has completely separate sets of surfaces
+ int numLODs;
+ int ofsLODs;
+
+ int ofsEnd; // end of file
+} md4Header_t;
+
+/*
+ * Here are the definitions for Ravensoft's model format of md4. Raven stores their
+ * playermodels in .mdr files, in some games, which are pretty much like the md4
+ * format implemented by ID soft. It seems like ID's original md4 stuff is not used at all.
+ * MDR is being used in EliteForce, JediKnight2 and Soldiers of Fortune2 (I think).
+ * So this comes in handy for anyone who wants to make it possible to load player
+ * models from these games.
+ * This format has bone tags, which is similar to the thing you have in md3 I suppose.
+ * Raven has released their version of md3view under GPL enabling me to add support
+ * to this codebase. Thanks to Steven Howes aka Skinner for helping with example
+ * source code.
+ *
+ * - Thilo Schulz (arny@ats.s.bawue.de)
+ */
+
+// If you want to enable support for Raven's .mdr / md4 format, uncomment the next
+// line.
+//#define RAVENMD4
+
+#ifdef RAVENMD4
+
+#define MDR_IDENT (('5'<<24)+('M'<<16)+('D'<<8)+'R')
+#define MDR_VERSION 2
+#define MDR_MAX_BONES 128
+
+typedef struct {
+ int boneIndex; // these are indexes into the boneReferences,
+ float boneWeight; // not the global per-frame bone list
+ vec3_t offset;
+} mdrWeight_t;
+
+typedef struct {
+ vec3_t normal;
+ vec2_t texCoords;
+ int numWeights;
+ mdrWeight_t weights[1]; // variable sized
+} mdrVertex_t;
+
+typedef struct {
+ int indexes[3];
+} mdrTriangle_t;
+
+typedef struct {
+ int ident;
+
+ char name[MAX_QPATH]; // polyset name
+ char shader[MAX_QPATH];
+ int shaderIndex; // for in-game use
+
+ int ofsHeader; // this will be a negative number
+
+ int numVerts;
+ int ofsVerts;
+
+ int numTriangles;
+ int ofsTriangles;
+
+ // Bone references are a set of ints representing all the bones
+ // present in any vertex weights for this surface. This is
+ // needed because a model may have surfaces that need to be
+ // drawn at different sort times, and we don't want to have
+ // to re-interpolate all the bones for each surface.
+ int numBoneReferences;
+ int ofsBoneReferences;
+
+ int ofsEnd; // next surface follows
+} mdrSurface_t;
+
+typedef struct {
+ float matrix[3][4];
+} mdrBone_t;
+
+typedef struct {
+ vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
+ vec3_t localOrigin; // midpoint of bounds, used for sphere cull
+ float radius; // dist from localOrigin to corner
+ char name[16];
+ mdrBone_t bones[1]; // [numBones]
+} mdrFrame_t;
+
+typedef struct {
+ unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple
+} mdrCompBone_t;
+
+typedef struct {
+ vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
+ vec3_t localOrigin; // midpoint of bounds, used for sphere cull
+ float radius; // dist from localOrigin to corner
+ mdrCompBone_t bones[1]; // [numBones]
+} mdrCompFrame_t;
+
+typedef struct {
+ int numSurfaces;
+ int ofsSurfaces; // first surface, others follow
+ int ofsEnd; // next lod follows
+} mdrLOD_t;
+
+typedef struct {
+ int boneIndex;
+ char name[32];
+} mdrTag_t;
+
+typedef struct {
+ int ident;
+ int version;
+
+ char name[MAX_QPATH]; // model name
+
+ // frames and bones are shared by all levels of detail
+ int numFrames;
+ int numBones;
+ int ofsFrames; // mdrFrame_t[numFrames]
+
+ // each level of detail has completely separate sets of surfaces
+ int numLODs;
+ int ofsLODs;
+
+ int numTags;
+ int ofsTags;
+
+ int ofsEnd; // end of file
+} mdrHeader_t;
+
+#endif
+
+/*
+==============================================================================
+
+ .BSP file format
+
+==============================================================================
+*/
+
+
+#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I')
+ // little-endian "IBSP"
+
+#define BSP_VERSION 46
+
+
+// there shouldn't be any problem with increasing these values at the
+// expense of more memory allocation in the utilities
+#define MAX_MAP_MODELS 0x400
+#define MAX_MAP_BRUSHES 0x8000
+#define MAX_MAP_ENTITIES 0x800
+#define MAX_MAP_ENTSTRING 0x40000
+#define MAX_MAP_SHADERS 0x400
+
+#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match!
+#define MAX_MAP_FOGS 0x100
+#define MAX_MAP_PLANES 0x20000
+#define MAX_MAP_NODES 0x20000
+#define MAX_MAP_BRUSHSIDES 0x20000
+#define MAX_MAP_LEAFS 0x20000
+#define MAX_MAP_LEAFFACES 0x20000
+#define MAX_MAP_LEAFBRUSHES 0x40000
+#define MAX_MAP_PORTALS 0x20000
+#define MAX_MAP_LIGHTING 0x800000
+#define MAX_MAP_LIGHTGRID 0x800000
+#define MAX_MAP_VISIBILITY 0x200000
+
+#define MAX_MAP_DRAW_SURFS 0x20000
+#define MAX_MAP_DRAW_VERTS 0x80000
+#define MAX_MAP_DRAW_INDEXES 0x80000
+
+
+// key / value pair sizes in the entities lump
+#define MAX_KEY 32
+#define MAX_VALUE 1024
+
+// the editor uses these predefined yaw angles to orient entities up or down
+#define ANGLE_UP -1
+#define ANGLE_DOWN -2
+
+#define LIGHTMAP_WIDTH 128
+#define LIGHTMAP_HEIGHT 128
+
+#define MAX_WORLD_COORD ( 128*1024 )
+#define MIN_WORLD_COORD ( -128*1024 )
+#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD )
+
+//=============================================================================
+
+
+typedef struct {
+ int fileofs, filelen;
+} lump_t;
+
+#define LUMP_ENTITIES 0
+#define LUMP_SHADERS 1
+#define LUMP_PLANES 2
+#define LUMP_NODES 3
+#define LUMP_LEAFS 4
+#define LUMP_LEAFSURFACES 5
+#define LUMP_LEAFBRUSHES 6
+#define LUMP_MODELS 7
+#define LUMP_BRUSHES 8
+#define LUMP_BRUSHSIDES 9
+#define LUMP_DRAWVERTS 10
+#define LUMP_DRAWINDEXES 11
+#define LUMP_FOGS 12
+#define LUMP_SURFACES 13
+#define LUMP_LIGHTMAPS 14
+#define LUMP_LIGHTGRID 15
+#define LUMP_VISIBILITY 16
+#define HEADER_LUMPS 17
+
+typedef struct {
+ int ident;
+ int version;
+
+ lump_t lumps[HEADER_LUMPS];
+} dheader_t;
+
+typedef struct {
+ float mins[3], maxs[3];
+ int firstSurface, numSurfaces;
+ int firstBrush, numBrushes;
+} dmodel_t;
+
+typedef struct {
+ char shader[MAX_QPATH];
+ int surfaceFlags;
+ int contentFlags;
+} dshader_t;
+
+// planes x^1 is allways the opposite of plane x
+
+typedef struct {
+ float normal[3];
+ float dist;
+} dplane_t;
+
+typedef struct {
+ int planeNum;
+ int children[2]; // negative numbers are -(leafs+1), not nodes
+ int mins[3]; // for frustom culling
+ int maxs[3];
+} dnode_t;
+
+typedef struct {
+ int cluster; // -1 = opaque cluster (do I still store these?)
+ int area;
+
+ int mins[3]; // for frustum culling
+ int maxs[3];
+
+ int firstLeafSurface;
+ int numLeafSurfaces;
+
+ int firstLeafBrush;
+ int numLeafBrushes;
+} dleaf_t;
+
+typedef struct {
+ int planeNum; // positive plane side faces out of the leaf
+ int shaderNum;
+} dbrushside_t;
+
+typedef struct {
+ int firstSide;
+ int numSides;
+ int shaderNum; // the shader that determines the contents flags
+} dbrush_t;
+
+typedef struct {
+ char shader[MAX_QPATH];
+ int brushNum;
+ int visibleSide; // the brush side that ray tests need to clip against (-1 == none)
+} dfog_t;
+
+typedef struct {
+ vec3_t xyz;
+ float st[2];
+ float lightmap[2];
+ vec3_t normal;
+ byte color[4];
+} drawVert_t;
+
+#define drawVert_t_cleared(x) drawVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0, 0}}
+
+typedef enum {
+ MST_BAD,
+ MST_PLANAR,
+ MST_PATCH,
+ MST_TRIANGLE_SOUP,
+ MST_FLARE
+} mapSurfaceType_t;
+
+typedef struct {
+ int shaderNum;
+ int fogNum;
+ int surfaceType;
+
+ int firstVert;
+ int numVerts;
+
+ int firstIndex;
+ int numIndexes;
+
+ int lightmapNum;
+ int lightmapX, lightmapY;
+ int lightmapWidth, lightmapHeight;
+
+ vec3_t lightmapOrigin;
+ vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds
+
+ int patchWidth;
+ int patchHeight;
+} dsurface_t;
+
+
+#endif
diff --git a/src/qcommon/surfaceflags.h b/src/qcommon/surfaceflags.h
new file mode 100644
index 0000000..31ece5c
--- /dev/null
+++ b/src/qcommon/surfaceflags.h
@@ -0,0 +1,91 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+// This file must be identical in the quake and utils directories
+
+// contents flags are seperate bits
+// a given brush can contribute multiple content bits
+
+// these definitions also need to be in q_shared.h!
+
+#define CONTENTS_SOLID 1 // an eye is never valid in a solid
+#define CONTENTS_LAVA 8
+#define CONTENTS_SLIME 16
+#define CONTENTS_WATER 32
+#define CONTENTS_FOG 64
+
+#define CONTENTS_NOTTEAM1 0x0080
+#define CONTENTS_NOTTEAM2 0x0100
+#define CONTENTS_NOBOTCLIP 0x0200
+
+#define CONTENTS_AREAPORTAL 0x8000
+
+#define CONTENTS_PLAYERCLIP 0x10000
+#define CONTENTS_MONSTERCLIP 0x20000
+//bot specific contents types
+#define CONTENTS_TELEPORTER 0x40000
+#define CONTENTS_JUMPPAD 0x80000
+#define CONTENTS_CLUSTERPORTAL 0x100000
+#define CONTENTS_DONOTENTER 0x200000
+#define CONTENTS_BOTCLIP 0x400000
+#define CONTENTS_MOVER 0x800000
+
+#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity
+
+#define CONTENTS_BODY 0x2000000 // should never be on a brush, only in game
+#define CONTENTS_CORPSE 0x4000000
+#define CONTENTS_DETAIL 0x8000000 // brushes not used for the bsp
+#define CONTENTS_STRUCTURAL 0x10000000 // brushes used for the bsp
+#define CONTENTS_TRANSLUCENT 0x20000000 // don't consume surface fragments inside
+#define CONTENTS_TRIGGER 0x40000000
+#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava)
+
+//TA: custominfoparms below
+#define CONTENTS_NOALIENBUILD 0x1000 //disallow alien building
+#define CONTENTS_NOHUMANBUILD 0x2000 //disallow alien building
+#define CONTENTS_NOBUILD 0x4000 //disallow alien building
+
+#define SURF_NODAMAGE 0x1 // never give falling damage
+#define SURF_SLICK 0x2 // effects game physics
+#define SURF_SKY 0x4 // lighting from environment map
+#define SURF_LADDER 0x8
+#define SURF_NOIMPACT 0x10 // don't make missile explosions
+#define SURF_NOMARKS 0x20 // don't leave missile marks
+#define SURF_FLESH 0x40 // make flesh sounds and effects
+#define SURF_NODRAW 0x80 // don't generate a drawsurface at all
+#define SURF_HINT 0x100 // make a primary bsp splitter
+#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes
+#define SURF_NOLIGHTMAP 0x400 // surface doesn't need a lightmap
+#define SURF_POINTLIGHT 0x800 // generate lighting info at vertexes
+#define SURF_METALSTEPS 0x1000 // clanking footsteps
+#define SURF_NOSTEPS 0x2000 // no footstep sounds
+#define SURF_NONSOLID 0x4000 // don't collide against curves with this set
+#define SURF_LIGHTFILTER 0x8000 // act as a light filter during q3map -light
+#define SURF_ALPHASHADOW 0x10000 // do per-pixel light shadow casting in q3map
+#define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies)
+#define SURF_DUST 0x40000 // leave a dust trail when walking on this surface
+
+//TA: custominfoparms below
+#define SURF_NOALIENBUILD 0x80000 //disallow alien building
+#define SURF_NOHUMANBUILD 0x100000 //disallow alien building
+#define SURF_NOBUILD 0x200000 //disallow alien building
diff --git a/src/renderer/tr_types.h b/src/renderer/tr_types.h
new file mode 100644
index 0000000..6db8c12
--- /dev/null
+++ b/src/renderer/tr_types.h
@@ -0,0 +1,234 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __TR_TYPES_H
+#define __TR_TYPES_H
+
+
+#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces
+#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing
+
+// renderfx flags
+#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items)
+#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites)
+#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob)
+#define RF_DEPTHHACK 8 // for view weapon Z crunching
+#define RF_NOSHADOW 64 // don't add stencil shadows
+
+#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin
+ // for lighting. This allows entities to sink into the floor
+ // with their origin going solid, and allows all parts of a
+ // player to get the same lighting
+#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane
+#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous
+ // animation without needing to know the frame count
+
+// refdef flags
+#define RDF_NOWORLDMODEL 1 // used for player configuration screen
+#define RDF_HYPERSPACE 4 // teleportation effect
+
+typedef struct {
+ vec3_t xyz;
+ float st[2];
+ byte modulate[4];
+} polyVert_t;
+
+typedef struct poly_s {
+ qhandle_t hShader;
+ int numVerts;
+ polyVert_t *verts;
+} poly_t;
+
+typedef enum {
+ RT_MODEL,
+ RT_POLY,
+ RT_SPRITE,
+ RT_BEAM,
+ RT_RAIL_CORE,
+ RT_RAIL_RINGS,
+ RT_LIGHTNING,
+ RT_PORTALSURFACE, // doesn't draw anything, just info for portals
+
+ RT_MAX_REF_ENTITY_TYPE
+} refEntityType_t;
+
+typedef struct {
+ refEntityType_t reType;
+ int renderfx;
+
+ qhandle_t hModel; // opaque type outside refresh
+
+ // most recent data
+ vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN)
+ float shadowPlane; // projection shadows go here, stencils go slightly lower
+
+ vec3_t axis[3]; // rotation vectors
+ qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale
+ float origin[3]; // also used as MODEL_BEAM's "from"
+ int frame; // also used as MODEL_BEAM's diameter
+
+ // previous data for frame interpolation
+ float oldorigin[3]; // also used as MODEL_BEAM's "to"
+ int oldframe;
+ float backlerp; // 0.0 = current, 1.0 = old
+
+ // texturing
+ int skinNum; // inline skin index
+ qhandle_t customSkin; // NULL for default skin
+ qhandle_t customShader; // use one image for the entire thing
+
+ // misc
+ byte shaderRGBA[4]; // colors used by rgbgen entity shaders
+ float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers
+ float shaderTime; // subtracted from refdef time to control effect start times
+
+ // extra sprite information
+ float radius;
+ float rotation;
+} refEntity_t;
+
+
+#define MAX_RENDER_STRINGS 8
+#define MAX_RENDER_STRING_LENGTH 32
+
+typedef struct {
+ int x, y, width, height;
+ float fov_x, fov_y;
+ vec3_t vieworg;
+ vec3_t viewaxis[3]; // transformation matrix
+
+ // time in milliseconds for shader effects and other time dependent rendering issues
+ int time;
+
+ int rdflags; // RDF_NOWORLDMODEL, etc
+
+ // 1 bits will prevent the associated area from rendering at all
+ byte areamask[MAX_MAP_AREA_BYTES];
+
+ // text messages for deform text shaders
+ char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
+} refdef_t;
+
+
+typedef enum {
+ STEREO_CENTER,
+ STEREO_LEFT,
+ STEREO_RIGHT
+} stereoFrame_t;
+
+
+/*
+** glconfig_t
+**
+** Contains variables specific to the OpenGL configuration
+** being run right now. These are constant once the OpenGL
+** subsystem is initialized.
+*/
+typedef enum {
+ TC_NONE,
+ TC_S3TC
+} textureCompression_t;
+
+typedef enum {
+ GLDRV_ICD, // driver is integrated with window system
+ // WARNING: there are tests that check for
+ // > GLDRV_ICD for minidriverness, so this
+ // should always be the lowest value in this
+ // enum set
+ GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver
+ GLDRV_VOODOO // driver is a 3Dfx standalone driver
+} glDriverType_t;
+
+typedef enum {
+ GLHW_GENERIC, // where everthing works the way it should
+ GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is
+ // the hardware type then there can NOT exist a secondary
+ // display adapter
+ GLHW_RIVA128, // where you can't interpolate alpha
+ GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures
+ GLHW_PERMEDIA2 // where you don't have src*dst
+} glHardwareType_t;
+
+typedef struct {
+ char renderer_string[MAX_STRING_CHARS];
+ char vendor_string[MAX_STRING_CHARS];
+ char version_string[MAX_STRING_CHARS];
+ char extensions_string[BIG_INFO_STRING];
+
+ int maxTextureSize; // queried from GL
+ int maxActiveTextures; // multitexture ability
+
+ int colorBits, depthBits, stencilBits;
+
+ glDriverType_t driverType;
+ glHardwareType_t hardwareType;
+
+ qboolean deviceSupportsGamma;
+ textureCompression_t textureCompression;
+ qboolean textureEnvAddAvailable;
+
+ int vidWidth, vidHeight;
+ // aspect is the screen's physical width / height, which may be different
+ // than scrWidth / scrHeight if the pixels are non-square
+ // normal screens should be 4/3, but wide aspect monitors may be 16/9
+ float windowAspect;
+
+ int displayFrequency;
+
+ // synonymous with "does rendering consume the entire screen?", therefore
+ // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that
+ // used CDS.
+ qboolean isFullscreen;
+ qboolean stereoEnabled;
+ qboolean smpActive; // dual processor
+
+ qboolean textureFilterAnisotropic;
+ int maxAnisotropy;
+
+} glconfig_t;
+
+// FIXME: VM should be OS agnostic .. in theory
+
+/*
+#ifdef Q3_VM
+
+#define _3DFX_DRIVER_NAME "Voodoo"
+#define OPENGL_DRIVER_NAME "Default"
+
+#elif defined(_WIN32)
+*/
+
+#if defined(Q3_VM) || defined(_WIN32)
+
+#define _3DFX_DRIVER_NAME "3dfxvgl"
+#define OPENGL_DRIVER_NAME "opengl32"
+
+#else
+
+#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so"
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524
+#define OPENGL_DRIVER_NAME "libGL.so.1"
+
+#endif // !defined _WIN32
+
+#endif // __TR_TYPES_H
diff --git a/src/tools/asm/README.Id b/src/tools/asm/README.Id
new file mode 100644
index 0000000..adacb50
--- /dev/null
+++ b/src/tools/asm/README.Id
@@ -0,0 +1,10 @@
+2002-10-25 Timothee Besset <ttimo@idsoftware.com>
+If you are looking for a faster version of the q3asm tool, try:
+http://www.icculus.org/~phaethon/q3/q3asm-turbo/q3asm-turbo.html
+
+2001-10-31 Timothee Besset <ttimo@idsoftware.com>
+updated from the $/source/q3asm code
+modified for portability and use with >= 1.31 mod source release
+
+the cmdlib.c cmdlib.h mathlib.h qfiles.h have been copied from
+$/source/common
diff --git a/src/tools/asm/cmdlib.c b/src/tools/asm/cmdlib.c
new file mode 100644
index 0000000..5929d95
--- /dev/null
+++ b/src/tools/asm/cmdlib.c
@@ -0,0 +1,1141 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cmdlib.c
+
+#include "cmdlib.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef WIN32
+#include <direct.h>
+#include <windows.h>
+#elif defined(NeXT)
+#include <libc.h>
+#else
+#include <unistd.h>
+#endif
+
+#define BASEDIRNAME "quake" // assumed to have a 2 or 3 following
+#define PATHSEPERATOR '/'
+
+// set these before calling CheckParm
+int myargc;
+char **myargv;
+
+char com_token[1024];
+qboolean com_eof;
+
+qboolean archive;
+char archivedir[1024];
+
+
+/*
+===================
+ExpandWildcards
+
+Mimic unix command line expansion
+===================
+*/
+#define MAX_EX_ARGC 1024
+int ex_argc;
+char *ex_argv[MAX_EX_ARGC];
+#ifdef _WIN32
+#include "io.h"
+void ExpandWildcards( int *argc, char ***argv )
+{
+ struct _finddata_t fileinfo;
+ intptr_t handle;
+ int i;
+ char filename[1024];
+ char filebase[1024];
+ char *path;
+
+ ex_argc = 0;
+ for (i=0 ; i<*argc ; i++)
+ {
+ path = (*argv)[i];
+ if ( path[0] == '-'
+ || ( !strstr(path, "*") && !strstr(path, "?") ) )
+ {
+ ex_argv[ex_argc++] = path;
+ continue;
+ }
+
+ handle = _findfirst (path, &fileinfo);
+ if (handle == -1)
+ return;
+
+ ExtractFilePath (path, filebase);
+
+ do
+ {
+ sprintf (filename, "%s%s", filebase, fileinfo.name);
+ ex_argv[ex_argc++] = copystring (filename);
+ } while (_findnext( handle, &fileinfo ) != -1);
+
+ _findclose (handle);
+ }
+
+ *argc = ex_argc;
+ *argv = ex_argv;
+}
+#else
+void ExpandWildcards (int *argc, char ***argv)
+{
+}
+#endif
+
+#ifdef WIN_ERROR
+#include <windows.h>
+/*
+=================
+Error
+
+For abnormal program terminations in windowed apps
+=================
+*/
+void Error( const char *error, ... )
+{
+ va_list argptr;
+ char text[1024];
+ char text2[1024];
+ int err;
+
+ err = GetLastError ();
+
+ va_start (argptr,error);
+ vsprintf (text, error,argptr);
+ va_end (argptr);
+
+ sprintf (text2, "%s\nGetLastError() = %i", text, err);
+ MessageBox(NULL, text2, "Error", 0 /* MB_OK */ );
+
+ exit (1);
+}
+
+#else
+/*
+=================
+Error
+
+For abnormal program terminations in console apps
+=================
+*/
+void Error( const char *error, ...)
+{
+ va_list argptr;
+
+ _printf ("\n************ ERROR ************\n");
+
+ va_start (argptr,error);
+ vprintf (error,argptr);
+ va_end (argptr);
+ _printf ("\r\n");
+
+ exit (1);
+}
+#endif
+
+// only printf if in verbose mode
+qboolean verbose = qfalse;
+void qprintf( const char *format, ... ) {
+ va_list argptr;
+
+ if (!verbose)
+ return;
+
+ va_start (argptr,format);
+ vprintf (format,argptr);
+ va_end (argptr);
+
+}
+
+#ifdef WIN32
+HWND hwndOut = NULL;
+qboolean lookedForServer = qfalse;
+UINT wm_BroadcastCommand = -1;
+#endif
+
+void _printf( const char *format, ... ) {
+ va_list argptr;
+ char text[4096];
+#ifdef WIN32
+ ATOM a;
+#endif
+ va_start (argptr,format);
+ vsprintf (text, format, argptr);
+ va_end (argptr);
+
+ printf("%s", text);
+
+#ifdef WIN32
+ if (!lookedForServer) {
+ lookedForServer = qtrue;
+ hwndOut = FindWindow(NULL, "Q3Map Process Server");
+ if (hwndOut) {
+ wm_BroadcastCommand = RegisterWindowMessage( "Q3MPS_BroadcastCommand" );
+ }
+ }
+ if (hwndOut) {
+ a = GlobalAddAtom(text);
+ PostMessage(hwndOut, wm_BroadcastCommand, 0, (LPARAM)a);
+ }
+#endif
+}
+
+
+/*
+
+qdir will hold the path up to the quake directory, including the slash
+
+ f:\quake\
+ /raid/quake/
+
+gamedir will hold qdir + the game directory (id1, id2, etc)
+
+ */
+
+char qdir[1024];
+char gamedir[1024];
+char writedir[1024];
+
+void SetQdirFromPath( const char *path )
+{
+ char temp[1024];
+ const char *c;
+ const char *sep;
+ int len, count;
+
+ if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':'))
+ { // path is partial
+ Q_getwd (temp);
+ strcat (temp, path);
+ path = temp;
+ }
+
+ // search for "quake2" in path
+
+ len = strlen(BASEDIRNAME);
+ for (c=path+strlen(path)-1 ; c != path ; c--)
+ {
+ int i;
+
+ if (!Q_strncasecmp (c, BASEDIRNAME, len))
+ {
+ //
+ //strncpy (qdir, path, c+len+2-path);
+ // the +2 assumes a 2 or 3 following quake which is not the
+ // case with a retail install
+ // so we need to add up how much to the next separator
+ sep = c + len;
+ count = 1;
+ while (*sep && *sep != '/' && *sep != '\\')
+ {
+ sep++;
+ count++;
+ }
+ strncpy (qdir, path, c+len+count-path);
+ qprintf ("qdir: %s\n", qdir);
+ for ( i = 0; i < strlen( qdir ); i++ )
+ {
+ if ( qdir[i] == '\\' )
+ qdir[i] = '/';
+ }
+
+ c += len+count;
+ while (*c)
+ {
+ if (*c == '/' || *c == '\\')
+ {
+ strncpy (gamedir, path, c+1-path);
+
+ for ( i = 0; i < strlen( gamedir ); i++ )
+ {
+ if ( gamedir[i] == '\\' )
+ gamedir[i] = '/';
+ }
+
+ qprintf ("gamedir: %s\n", gamedir);
+
+ if ( !writedir[0] )
+ strcpy( writedir, gamedir );
+ else if ( writedir[strlen( writedir )-1] != '/' )
+ {
+ writedir[strlen( writedir )] = '/';
+ writedir[strlen( writedir )+1] = 0;
+ }
+
+ return;
+ }
+ c++;
+ }
+ Error ("No gamedir in %s", path);
+ return;
+ }
+ }
+ Error ("SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path);
+}
+
+char *ExpandArg (const char *path)
+{
+ static char full[1024];
+
+ if (path[0] != '/' && path[0] != '\\' && path[1] != ':')
+ {
+ Q_getwd (full);
+ strcat (full, path);
+ }
+ else
+ strcpy (full, path);
+ return full;
+}
+
+char *ExpandPath (const char *path)
+{
+ static char full[1024];
+ if (!qdir[0])
+ Error ("ExpandPath called without qdir set");
+ if (path[0] == '/' || path[0] == '\\' || path[1] == ':') {
+ strcpy( full, path );
+ return full;
+ }
+ sprintf (full, "%s%s", qdir, path);
+ return full;
+}
+
+char *ExpandGamePath (const char *path)
+{
+ static char full[1024];
+ if (!qdir[0])
+ Error ("ExpandGamePath called without qdir set");
+ if (path[0] == '/' || path[0] == '\\' || path[1] == ':') {
+ strcpy( full, path );
+ return full;
+ }
+ sprintf (full, "%s%s", gamedir, path);
+ return full;
+}
+
+char *ExpandPathAndArchive (const char *path)
+{
+ char *expanded;
+ char archivename[1024];
+
+ expanded = ExpandPath (path);
+
+ if (archive)
+ {
+ sprintf (archivename, "%s/%s", archivedir, path);
+ QCopyFile (expanded, archivename);
+ }
+ return expanded;
+}
+
+
+char *copystring(const char *s)
+{
+ char *b;
+ b = malloc(strlen(s)+1);
+ strcpy (b, s);
+ return b;
+}
+
+
+
+/*
+================
+I_FloatTime
+================
+*/
+double I_FloatTime (void)
+{
+ time_t t;
+
+ time (&t);
+
+ return t;
+#if 0
+// more precise, less portable
+ struct timeval tp;
+ struct timezone tzp;
+ static int secbase;
+
+ gettimeofday(&tp, &tzp);
+
+ if (!secbase)
+ {
+ secbase = tp.tv_sec;
+ return tp.tv_usec/1000000.0;
+ }
+
+ return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;
+#endif
+}
+
+void Q_getwd (char *out)
+{
+ int i = 0;
+
+#ifdef WIN32
+ if (_getcwd (out, 256) == NULL)
+ strcpy(out, "."); /* shrug */
+ strcat (out, "\\");
+#else
+ if (getcwd (out, 256) == NULL)
+ strcpy(out, "."); /* shrug */
+ strcat (out, "/");
+#endif
+
+ while ( out[i] != 0 )
+ {
+ if ( out[i] == '\\' )
+ out[i] = '/';
+ i++;
+ }
+}
+
+
+void Q_mkdir (const char *path)
+{
+#ifdef WIN32
+ if (_mkdir (path) != -1)
+ return;
+#else
+ if (mkdir (path, 0777) != -1)
+ return;
+#endif
+ if (errno != EEXIST)
+ Error ("mkdir %s: %s",path, strerror(errno));
+}
+
+/*
+============
+FileTime
+
+returns -1 if not present
+============
+*/
+int FileTime (const char *path)
+{
+ struct stat buf;
+
+ if (stat (path,&buf) == -1)
+ return -1;
+
+ return buf.st_mtime;
+}
+
+
+
+/*
+==============
+COM_Parse
+
+Parse a token out of a string
+==============
+*/
+char *COM_Parse (char *data)
+{
+ int c;
+ int len;
+
+ len = 0;
+ com_token[0] = 0;
+
+ if (!data)
+ return NULL;
+
+// skip whitespace
+skipwhite:
+ while ( (c = *data) <= ' ')
+ {
+ if (c == 0)
+ {
+ com_eof = qtrue;
+ return NULL; // end of file;
+ }
+ data++;
+ }
+
+// skip // comments
+ if (c=='/' && data[1] == '/')
+ {
+ while (*data && *data != '\n')
+ data++;
+ goto skipwhite;
+ }
+
+
+// handle quoted strings specially
+ if (c == '\"')
+ {
+ data++;
+ do
+ {
+ c = *data++;
+ if (c=='\"')
+ {
+ com_token[len] = 0;
+ return data;
+ }
+ com_token[len] = c;
+ len++;
+ } while (1);
+ }
+
+// parse single characters
+ if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
+ {
+ com_token[len] = c;
+ len++;
+ com_token[len] = 0;
+ return data+1;
+ }
+
+// parse a regular word
+ do
+ {
+ com_token[len] = c;
+ data++;
+ len++;
+ c = *data;
+ if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
+ break;
+ } while (c>32);
+
+ com_token[len] = 0;
+ return data;
+}
+
+
+int Q_strncasecmp (const char *s1, const char *s2, int n)
+{
+ int c1, c2;
+
+ do
+ {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (!n--)
+ return 0; // strings are equal until end point
+
+ if (c1 != c2)
+ {
+ if (c1 >= 'a' && c1 <= 'z')
+ c1 -= ('a' - 'A');
+ if (c2 >= 'a' && c2 <= 'z')
+ c2 -= ('a' - 'A');
+ if (c1 != c2)
+ return -1; // strings not equal
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+int Q_stricmp (const char *s1, const char *s2)
+{
+ return Q_strncasecmp (s1, s2, 99999);
+}
+
+
+char *strupr (char *start)
+{
+ char *in;
+ in = start;
+ while (*in)
+ {
+ *in = toupper(*in);
+ in++;
+ }
+ return start;
+}
+
+char *strlower (char *start)
+{
+ char *in;
+ in = start;
+ while (*in)
+ {
+ *in = tolower(*in);
+ in++;
+ }
+ return start;
+}
+
+
+/*
+=============================================================================
+
+ MISC FUNCTIONS
+
+=============================================================================
+*/
+
+
+/*
+=================
+CheckParm
+
+Checks for the given parameter in the program's command line arguments
+Returns the argument number (1 to argc-1) or 0 if not present
+=================
+*/
+int CheckParm (const char *check)
+{
+ int i;
+
+ for (i = 1;i<myargc;i++)
+ {
+ if ( !Q_stricmp(check, myargv[i]) )
+ return i;
+ }
+
+ return 0;
+}
+
+
+
+/*
+================
+Q_filelength
+================
+*/
+int Q_filelength (FILE *f)
+{
+ int pos;
+ int end;
+
+ pos = ftell (f);
+ fseek (f, 0, SEEK_END);
+ end = ftell (f);
+ fseek (f, pos, SEEK_SET);
+
+ return end;
+}
+
+#ifdef MAX_PATH
+#undef MAX_PATH
+#endif
+#define MAX_PATH 4096
+static FILE* myfopen(const char* filename, const char* mode)
+{
+ char* p;
+ char fn[MAX_PATH];
+
+ fn[0] = '\0';
+ strncat(fn, filename, sizeof(fn)-1);
+
+ for(p=fn;*p;++p) if(*p == '\\') *p = '/';
+
+ return fopen(fn, mode);
+}
+
+
+FILE *SafeOpenWrite (const char *filename)
+{
+ FILE *f;
+
+ f = myfopen(filename, "wb");
+
+ if (!f)
+ Error ("Error opening %s: %s",filename,strerror(errno));
+
+ return f;
+}
+
+FILE *SafeOpenRead (const char *filename)
+{
+ FILE *f;
+
+ f = myfopen(filename, "rb");
+
+ if (!f)
+ Error ("Error opening %s: %s",filename,strerror(errno));
+
+ return f;
+}
+
+
+void SafeRead (FILE *f, void *buffer, int count)
+{
+ if ( fread (buffer, 1, count, f) != (size_t)count)
+ Error ("File read failure");
+}
+
+
+void SafeWrite (FILE *f, const void *buffer, int count)
+{
+ if (fwrite (buffer, 1, count, f) != (size_t)count)
+ Error ("File write failure");
+}
+
+
+/*
+==============
+FileExists
+==============
+*/
+qboolean FileExists (const char *filename)
+{
+ FILE *f;
+
+ f = myfopen (filename, "r");
+ if (!f)
+ return qfalse;
+ fclose (f);
+ return qtrue;
+}
+
+/*
+==============
+LoadFile
+==============
+*/
+int LoadFile( const char *filename, void **bufferptr )
+{
+ FILE *f;
+ int length;
+ void *buffer;
+
+ f = SafeOpenRead (filename);
+ length = Q_filelength (f);
+ buffer = malloc (length+1);
+ ((char *)buffer)[length] = 0;
+ SafeRead (f, buffer, length);
+ fclose (f);
+
+ *bufferptr = buffer;
+ return length;
+}
+
+
+/*
+==============
+LoadFileBlock
+-
+rounds up memory allocation to 4K boundry
+-
+==============
+*/
+int LoadFileBlock( const char *filename, void **bufferptr )
+{
+ FILE *f;
+ int length, nBlock, nAllocSize;
+ void *buffer;
+
+ f = SafeOpenRead (filename);
+ length = Q_filelength (f);
+ nAllocSize = length;
+ nBlock = nAllocSize % MEM_BLOCKSIZE;
+ if ( nBlock > 0) {
+ nAllocSize += MEM_BLOCKSIZE - nBlock;
+ }
+ buffer = malloc (nAllocSize+1);
+ memset(buffer, 0, nAllocSize+1);
+ SafeRead (f, buffer, length);
+ fclose (f);
+
+ *bufferptr = buffer;
+ return length;
+}
+
+
+/*
+==============
+TryLoadFile
+
+Allows failure
+==============
+*/
+int TryLoadFile (const char *filename, void **bufferptr)
+{
+ FILE *f;
+ int length;
+ void *buffer;
+
+ *bufferptr = NULL;
+
+ f = myfopen (filename, "rb");
+ if (!f)
+ return -1;
+ length = Q_filelength (f);
+ buffer = malloc (length+1);
+ ((char *)buffer)[length] = 0;
+ SafeRead (f, buffer, length);
+ fclose (f);
+
+ *bufferptr = buffer;
+ return length;
+}
+
+
+/*
+==============
+SaveFile
+==============
+*/
+void SaveFile (const char *filename, const void *buffer, int count)
+{
+ FILE *f;
+
+ f = SafeOpenWrite (filename);
+ SafeWrite (f, buffer, count);
+ fclose (f);
+}
+
+
+
+void DefaultExtension (char *path, const char *extension)
+{
+ char *src;
+//
+// if path doesnt have a .EXT, append extension
+// (extension should include the .)
+//
+ src = path + strlen(path) - 1;
+
+ while (*src != '/' && *src != '\\' && src != path)
+ {
+ if (*src == '.')
+ return; // it has an extension
+ src--;
+ }
+
+ strcat (path, extension);
+}
+
+
+void DefaultPath (char *path, const char *basepath)
+{
+ char temp[128];
+
+ if (path[0] == PATHSEPERATOR)
+ return; // absolute path location
+ strcpy (temp,path);
+ strcpy (path,basepath);
+ strcat (path,temp);
+}
+
+
+void StripFilename (char *path)
+{
+ int length;
+
+ length = strlen(path)-1;
+ while (length > 0 && path[length] != PATHSEPERATOR)
+ length--;
+ path[length] = 0;
+}
+
+void StripExtension (char *path)
+{
+ int length;
+
+ length = strlen(path)-1;
+ while (length > 0 && path[length] != '.')
+ {
+ length--;
+ if (path[length] == '/')
+ return; // no extension
+ }
+ if (length)
+ path[length] = 0;
+}
+
+
+/*
+====================
+Extract file parts
+====================
+*/
+// FIXME: should include the slash, otherwise
+// backing to an empty path will be wrong when appending a slash
+void ExtractFilePath (const char *path, char *dest)
+{
+ const char *src;
+
+ src = path + strlen(path) - 1;
+
+//
+// back up until a \ or the start
+//
+ while (src != path && *(src-1) != '\\' && *(src-1) != '/')
+ src--;
+
+ memcpy (dest, path, src-path);
+ dest[src-path] = 0;
+}
+
+void ExtractFileBase (const char *path, char *dest)
+{
+ const char *src;
+
+ src = path + strlen(path) - 1;
+
+//
+// back up until a \ or the start
+//
+ while (src != path && *(src-1) != PATHSEPERATOR)
+ src--;
+
+ while (*src && *src != '.')
+ {
+ *dest++ = *src++;
+ }
+ *dest = 0;
+}
+
+void ExtractFileExtension (const char *path, char *dest)
+{
+ const char *src;
+
+ src = path + strlen(path) - 1;
+
+//
+// back up until a . or the start
+//
+ while (src != path && *(src-1) != '.')
+ src--;
+ if (src == path)
+ {
+ *dest = 0; // no extension
+ return;
+ }
+
+ strcpy (dest,src);
+}
+
+
+/*
+==============
+ParseNum / ParseHex
+==============
+*/
+int ParseHex (const char *hex)
+{
+ const char *str;
+ int num;
+
+ num = 0;
+ str = hex;
+
+ while (*str)
+ {
+ num <<= 4;
+ if (*str >= '0' && *str <= '9')
+ num += *str-'0';
+ else if (*str >= 'a' && *str <= 'f')
+ num += 10 + *str-'a';
+ else if (*str >= 'A' && *str <= 'F')
+ num += 10 + *str-'A';
+ else
+ Error ("Bad hex number: %s",hex);
+ str++;
+ }
+
+ return num;
+}
+
+
+int ParseNum (const char *str)
+{
+ if (str[0] == '$')
+ return ParseHex (str+1);
+ if (str[0] == '0' && str[1] == 'x')
+ return ParseHex (str+2);
+ return atol (str);
+}
+
+
+
+/*
+============================================================================
+
+ BYTE ORDER FUNCTIONS
+
+============================================================================
+*/
+
+short ShortSwap (short l)
+{
+ byte b1,b2;
+
+ b1 = l&255;
+ b2 = (l>>8)&255;
+
+ return (b1<<8) + b2;
+}
+
+int LongSwap (int l)
+{
+ byte b1,b2,b3,b4;
+
+ b1 = l&255;
+ b2 = (l>>8)&255;
+ b3 = (l>>16)&255;
+ b4 = (l>>24)&255;
+
+ return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+typedef union {
+ float f;
+ unsigned int i;
+} _FloatByteUnion;
+
+float FloatSwap (const float *f) {
+ _FloatByteUnion out;
+
+ out.f = *f;
+ out.i = LongSwap(out.i);
+
+ return out.f;
+}
+
+//=======================================================
+
+
+// FIXME: byte swap?
+
+// this is a 16 bit, non-reflected CRC using the polynomial 0x1021
+// and the initial and final xor values shown below... in other words, the
+// CCITT standard CRC used by XMODEM
+
+#define CRC_INIT_VALUE 0xffff
+#define CRC_XOR_VALUE 0x0000
+
+static unsigned short crctable[256] =
+{
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
+ 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
+ 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
+ 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
+ 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
+ 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
+ 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
+ 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
+ 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
+ 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
+ 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
+ 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
+ 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
+ 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
+ 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+ 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
+ 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
+ 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
+ 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
+ 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
+ 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
+ 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
+ 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
+};
+
+void CRC_Init(unsigned short *crcvalue)
+{
+ *crcvalue = CRC_INIT_VALUE;
+}
+
+void CRC_ProcessByte(unsigned short *crcvalue, byte data)
+{
+ *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data];
+}
+
+unsigned short CRC_Value(unsigned short crcvalue)
+{
+ return crcvalue ^ CRC_XOR_VALUE;
+}
+//=============================================================================
+
+/*
+============
+CreatePath
+============
+*/
+void CreatePath (const char *path)
+{
+ const char *ofs;
+ char c;
+ char dir[1024];
+
+#ifdef _WIN32
+ int olddrive = -1;
+
+ if ( path[1] == ':' )
+ {
+ olddrive = _getdrive();
+ _chdrive( toupper( path[0] ) - 'A' + 1 );
+ }
+#endif
+
+ if (path[1] == ':')
+ path += 2;
+
+ for (ofs = path+1 ; *ofs ; ofs++)
+ {
+ c = *ofs;
+ if (c == '/' || c == '\\')
+ { // create the directory
+ memcpy( dir, path, ofs - path );
+ dir[ ofs - path ] = 0;
+ Q_mkdir( dir );
+ }
+ }
+
+#ifdef _WIN32
+ if ( olddrive != -1 )
+ {
+ _chdrive( olddrive );
+ }
+#endif
+}
+
+
+/*
+============
+QCopyFile
+
+ Used to archive source files
+============
+*/
+void QCopyFile (const char *from, const char *to)
+{
+ void *buffer;
+ int length;
+
+ length = LoadFile (from, &buffer);
+ CreatePath (to);
+ SaveFile (to, buffer, length);
+ free (buffer);
+}
diff --git a/src/tools/asm/cmdlib.h b/src/tools/asm/cmdlib.h
new file mode 100644
index 0000000..3b2f2db
--- /dev/null
+++ b/src/tools/asm/cmdlib.h
@@ -0,0 +1,153 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cmdlib.h
+
+#ifndef __CMDLIB__
+#define __CMDLIB__
+
+#ifdef _MSC_VER
+#pragma warning(disable : 4244) // MIPS
+#pragma warning(disable : 4136) // X86
+#pragma warning(disable : 4051) // ALPHA
+
+#pragma warning(disable : 4018) // signed/unsigned mismatch
+#pragma warning(disable : 4305) // truncate from double to float
+
+#pragma check_stack(off)
+
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <time.h>
+#include <stdarg.h>
+
+#ifdef _MSC_VER
+
+#pragma intrinsic( memset, memcpy )
+
+#endif
+
+#ifndef __BYTEBOOL__
+#define __BYTEBOOL__
+typedef enum { qfalse, qtrue } qboolean;
+typedef unsigned char byte;
+#endif
+
+#define MAX_OS_PATH 1024
+#define MEM_BLOCKSIZE 4096
+
+// the dec offsetof macro doesnt work very well...
+#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier)
+
+
+// set these before calling CheckParm
+extern int myargc;
+extern char **myargv;
+
+char *strupr (char *in);
+char *strlower (char *in);
+int Q_strncasecmp( const char *s1, const char *s2, int n );
+int Q_stricmp( const char *s1, const char *s2 );
+void Q_getwd( char *out );
+
+int Q_filelength (FILE *f);
+int FileTime( const char *path );
+
+void Q_mkdir( const char *path );
+
+extern char qdir[1024];
+extern char gamedir[1024];
+extern char writedir[1024];
+void SetQdirFromPath( const char *path );
+char *ExpandArg( const char *path ); // from cmd line
+char *ExpandPath( const char *path ); // from scripts
+char *ExpandGamePath (const char *path);
+char *ExpandPathAndArchive( const char *path );
+
+
+double I_FloatTime( void );
+
+void Error( const char *error, ... );
+int CheckParm( const char *check );
+
+FILE *SafeOpenWrite( const char *filename );
+FILE *SafeOpenRead( const char *filename );
+void SafeRead (FILE *f, void *buffer, int count);
+void SafeWrite (FILE *f, const void *buffer, int count);
+
+int LoadFile( const char *filename, void **bufferptr );
+int LoadFileBlock( const char *filename, void **bufferptr );
+int TryLoadFile( const char *filename, void **bufferptr );
+void SaveFile( const char *filename, const void *buffer, int count );
+qboolean FileExists( const char *filename );
+
+void DefaultExtension( char *path, const char *extension );
+void DefaultPath( char *path, const char *basepath );
+void StripFilename( char *path );
+void StripExtension( char *path );
+
+void ExtractFilePath( const char *path, char *dest );
+void ExtractFileBase( const char *path, char *dest );
+void ExtractFileExtension( const char *path, char *dest );
+
+int ParseNum (const char *str);
+
+char *COM_Parse (char *data);
+
+extern char com_token[1024];
+extern qboolean com_eof;
+
+char *copystring(const char *s);
+
+
+void CRC_Init(unsigned short *crcvalue);
+void CRC_ProcessByte(unsigned short *crcvalue, byte data);
+unsigned short CRC_Value(unsigned short crcvalue);
+
+void CreatePath( const char *path );
+void QCopyFile( const char *from, const char *to );
+
+extern qboolean archive;
+extern char archivedir[1024];
+
+
+extern qboolean verbose;
+void qprintf( const char *format, ... );
+void _printf( const char *format, ... );
+
+void ExpandWildcards( int *argc, char ***argv );
+
+
+// for compression routines
+typedef struct
+{
+ void *data;
+ int count, width, height;
+} cblock_t;
+
+
+#endif
diff --git a/src/tools/asm/lib.txt b/src/tools/asm/lib.txt
new file mode 100644
index 0000000..737a030
--- /dev/null
+++ b/src/tools/asm/lib.txt
@@ -0,0 +1,31 @@
+
+strlen
+strcasecmp
+tolower
+strcat
+strncpy
+strcmp
+strcpy
+strchr
+
+vsprintf
+
+memcpy
+memset
+rand
+
+atoi
+atof
+
+abs
+
+floor
+fabs
+tan
+atan
+sqrt
+log
+cos
+sin
+atan2
+
diff --git a/src/tools/asm/mathlib.h b/src/tools/asm/mathlib.h
new file mode 100644
index 0000000..71bbabb
--- /dev/null
+++ b/src/tools/asm/mathlib.h
@@ -0,0 +1,95 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#ifndef __MATHLIB__
+#define __MATHLIB__
+
+// mathlib.h
+
+#include <math.h>
+
+#ifdef DOUBLEVEC_T
+typedef double vec_t;
+#else
+typedef float vec_t;
+#endif
+typedef vec_t vec2_t[3];
+typedef vec_t vec3_t[3];
+typedef vec_t vec4_t[4];
+
+#define SIDE_FRONT 0
+#define SIDE_ON 2
+#define SIDE_BACK 1
+#define SIDE_CROSS -2
+
+#define Q_PI 3.14159265358979323846
+#define DEG2RAD( a ) ( ( (a) * Q_PI ) / 180.0F )
+#define RAD2DEG( a ) ( ( (a) * 180.0f ) / Q_PI )
+
+extern vec3_t vec3_origin;
+
+#define EQUAL_EPSILON 0.001
+
+// plane types are used to speed some tests
+// 0-2 are axial planes
+#define PLANE_X 0
+#define PLANE_Y 1
+#define PLANE_Z 2
+#define PLANE_NON_AXIAL 3
+
+qboolean VectorCompare( const vec3_t v1, const vec3_t v2 );
+
+#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2])
+#define VectorSubtract(a,b,c) {c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];}
+#define VectorAdd(a,b,c) {c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];}
+#define VectorCopy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];}
+#define VectorScale(a,b,c) {c[0]=b*a[0];c[1]=b*a[1];c[2]=b*a[2];}
+#define VectorClear(x) {x[0] = x[1] = x[2] = 0;}
+#define VectorNegate(x) {x[0]=-x[0];x[1]=-x[1];x[2]=-x[2];}
+void Vec10Copy( vec_t *in, vec_t *out );
+
+vec_t Q_rint (vec_t in);
+vec_t _DotProduct (vec3_t v1, vec3_t v2);
+void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out);
+void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out);
+void _VectorCopy (vec3_t in, vec3_t out);
+void _VectorScale (vec3_t v, vec_t scale, vec3_t out);
+
+double VectorLength( const vec3_t v );
+
+void VectorMA( const vec3_t va, double scale, const vec3_t vb, vec3_t vc );
+
+void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross );
+vec_t VectorNormalize( const vec3_t in, vec3_t out );
+vec_t ColorNormalize( const vec3_t in, vec3_t out );
+void VectorInverse (vec3_t v);
+
+void ClearBounds (vec3_t mins, vec3_t maxs);
+void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs );
+
+qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c );
+
+void NormalToLatLong( const vec3_t normal, byte bytes[2] );
+
+int PlaneTypeForNormal (vec3_t normal);
+
+#endif
diff --git a/src/tools/asm/notes.txt b/src/tools/asm/notes.txt
new file mode 100644
index 0000000..63297f3
--- /dev/null
+++ b/src/tools/asm/notes.txt
@@ -0,0 +1,16 @@
+
+don't do any paramter conversion (double to float, etc)
+
+
+
+Why?
+
+Security.
+Portability.
+
+It may be more aproachable.
+
+can still use regular dlls for development purposes
+
+lcc
+q3asm
diff --git a/src/tools/asm/ops.txt b/src/tools/asm/ops.txt
new file mode 100644
index 0000000..e897f49
--- /dev/null
+++ b/src/tools/asm/ops.txt
@@ -0,0 +1,132 @@
+CNSTF,
+CNSTI,
+CNSTP,
+CNSTU,
+
+ARGB,
+ARGF,
+ARGI,
+ARGP,
+ARGU,
+
+ASGNB,
+ASGNF,
+ASGNI,
+ASGNP,
+ASGNU,
+
+INDIRB,
+INDIRF,
+INDIRI,
+INDIRP,
+INDIRU,
+
+CVFF,
+CVFI,
+
+CVIF,
+CVII,
+CVIU,
+
+CVPU,
+
+CVUI,
+CVUP,
+CVUU,
+
+NEGF,
+NEGI,
+
+CALLB,
+CALLF,
+CALLI,
+CALLP,
+CALLU,
+CALLV,
+
+RETF,
+RETI,
+RETP,
+RETU,
+RETV,
+
+ADDRGP,
+
+ADDRFP,
+
+ADDRLP,
+
+ADDF,
+ADDI,
+ADDP,
+ADDU,
+
+SUBF,
+SUBI,
+SUBP,
+SUBU,
+
+LSHI,
+LSHU,
+
+MODI,
+MODU,
+
+RSHI,
+RSHU,
+
+BANDI,
+BANDU,
+
+BCOMI,
+BCOMU,
+
+BORI,
+BORU,
+
+BXORI,
+BXORU,
+
+DIVF,
+DIVI,
+DIVU,
+
+MULF,
+MULI,
+MULU,
+
+EQF,
+EQI,
+EQU,
+
+GEF,
+GEI,
+GEU,
+
+GTF,
+GTI,
+GTU,
+
+LEF,
+LEI,
+LEU,
+
+LTF,
+LTI,
+LTU,
+
+NEF,
+NEI,
+NEU,
+
+JUMPV,
+
+LABELV,
+
+LOADB,
+LOADF,
+LOADI,
+LOADP,
+LOADU,
+
+
diff --git a/src/tools/asm/opstrings.h b/src/tools/asm/opstrings.h
new file mode 100644
index 0000000..0bf81ab
--- /dev/null
+++ b/src/tools/asm/opstrings.h
@@ -0,0 +1,176 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+{ "BREAK", OP_BREAK },
+
+{ "CNSTF4", OP_CONST },
+{ "CNSTI4", OP_CONST },
+{ "CNSTP4", OP_CONST },
+{ "CNSTU4", OP_CONST },
+
+{ "CNSTI2", OP_CONST },
+{ "CNSTU2", OP_CONST },
+
+{ "CNSTI1", OP_CONST },
+{ "CNSTU1", OP_CONST },
+
+//{ "ARGB", OP_ARG },
+//{ "ARGF", OP_ARG },
+//{ "ARGI", OP_ARG },
+//{ "ARGP", OP_ARG },
+//{ "ARGU", OP_ARG },
+
+{ "ASGNB", OP_BLOCK_COPY },
+{ "ASGNF4", OP_STORE4 },
+{ "ASGNI4", OP_STORE4 },
+{ "ASGNP4", OP_STORE4 },
+{ "ASGNU4", OP_STORE4 },
+
+{ "ASGNI2", OP_STORE2 },
+{ "ASGNU2", OP_STORE2 },
+
+{ "ASGNI1", OP_STORE1 },
+{ "ASGNU1", OP_STORE1 },
+
+{ "INDIRB", OP_IGNORE }, // block copy deals with this
+{ "INDIRF4", OP_LOAD4 },
+{ "INDIRI4", OP_LOAD4 },
+{ "INDIRP4", OP_LOAD4 },
+{ "INDIRU4", OP_LOAD4 },
+
+{ "INDIRI2", OP_LOAD2 },
+{ "INDIRU2", OP_LOAD2 },
+
+{ "INDIRI1", OP_LOAD1 },
+{ "INDIRU1", OP_LOAD1 },
+
+{ "CVFF4", OP_UNDEF },
+{ "CVFI4", OP_CVFI },
+
+{ "CVIF4", OP_CVIF },
+{ "CVII4", OP_SEX8 }, // will be either SEX8 or SEX16
+{ "CVII1", OP_IGNORE },
+{ "CVII2", OP_IGNORE },
+{ "CVIU4", OP_IGNORE },
+
+{ "CVPU4", OP_IGNORE },
+
+{ "CVUI4", OP_IGNORE },
+{ "CVUP4", OP_IGNORE },
+{ "CVUU4", OP_IGNORE },
+
+{ "CVUU1", OP_IGNORE },
+
+{ "NEGF4", OP_NEGF },
+{ "NEGI4", OP_NEGI },
+
+//{ "CALLB", OP_UNDEF },
+//{ "CALLF", OP_UNDEF },
+//{ "CALLI", OP_UNDEF },
+//{ "CALLP", OP_UNDEF },
+//{ "CALLU", OP_UNDEF },
+//{ "CALLV", OP_CALL },
+
+//{ "RETF", OP_UNDEF },
+//{ "RETI", OP_UNDEF },
+//{ "RETP", OP_UNDEF },
+//{ "RETU", OP_UNDEF },
+//{ "RETV", OP_UNDEF },
+
+{ "ADDRGP4", OP_CONST },
+
+//{ "ADDRFP", OP_PARM },
+//{ "ADDRLP", OP_LOCAL },
+
+{ "ADDF4", OP_ADDF },
+{ "ADDI4", OP_ADD },
+{ "ADDP4", OP_ADD },
+{ "ADDP", OP_ADD },
+{ "ADDU4", OP_ADD },
+
+{ "SUBF4", OP_SUBF },
+{ "SUBI4", OP_SUB },
+{ "SUBP4", OP_SUB },
+{ "SUBU4", OP_SUB },
+
+{ "LSHI4", OP_LSH },
+{ "LSHU4", OP_LSH },
+
+{ "MODI4", OP_MODI },
+{ "MODU4", OP_MODU },
+
+{ "RSHI4", OP_RSHI },
+{ "RSHU4", OP_RSHU },
+
+{ "BANDI4", OP_BAND },
+{ "BANDU4", OP_BAND },
+
+{ "BCOMI4", OP_BCOM },
+{ "BCOMU4", OP_BCOM },
+
+{ "BORI4", OP_BOR },
+{ "BORU4", OP_BOR },
+
+{ "BXORI4", OP_BXOR },
+{ "BXORU4", OP_BXOR },
+
+{ "DIVF4", OP_DIVF },
+{ "DIVI4", OP_DIVI },
+{ "DIVU4", OP_DIVU },
+
+{ "MULF4", OP_MULF },
+{ "MULI4", OP_MULI },
+{ "MULU4", OP_MULU },
+
+{ "EQF4", OP_EQF },
+{ "EQI4", OP_EQ },
+{ "EQU4", OP_EQ },
+
+{ "GEF4", OP_GEF },
+{ "GEI4", OP_GEI },
+{ "GEU4", OP_GEU },
+
+{ "GTF4", OP_GTF },
+{ "GTI4", OP_GTI },
+{ "GTU4", OP_GTU },
+
+{ "LEF4", OP_LEF },
+{ "LEI4", OP_LEI },
+{ "LEU4", OP_LEU },
+
+{ "LTF4", OP_LTF },
+{ "LTI4", OP_LTI },
+{ "LTU4", OP_LTU },
+
+{ "NEF4", OP_NEF },
+{ "NEI4", OP_NE },
+{ "NEU4", OP_NE },
+
+{ "JUMPV", OP_JUMP },
+
+{ "LOADB4", OP_UNDEF },
+{ "LOADF4", OP_UNDEF },
+{ "LOADI4", OP_UNDEF },
+{ "LOADP4", OP_UNDEF },
+{ "LOADU4", OP_UNDEF },
+
+
diff --git a/src/tools/asm/q3asm.c b/src/tools/asm/q3asm.c
new file mode 100644
index 0000000..0e35246
--- /dev/null
+++ b/src/tools/asm/q3asm.c
@@ -0,0 +1,1648 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "../../qcommon/q_platform.h"
+#include "cmdlib.h"
+#include "mathlib.h"
+#include "../../qcommon/qfiles.h"
+
+/* 19079 total symbols in FI, 2002 Jan 23 */
+#define DEFAULT_HASHTABLE_SIZE 2048
+
+char outputFilename[MAX_OS_PATH];
+
+// the zero page size is just used for detecting run time faults
+#define ZERO_PAGE_SIZE 0 // 256
+
+typedef enum {
+ OP_UNDEF,
+
+ OP_IGNORE,
+
+ OP_BREAK,
+
+ OP_ENTER,
+ OP_LEAVE,
+ OP_CALL,
+ OP_PUSH,
+ OP_POP,
+
+ OP_CONST,
+ OP_LOCAL,
+
+ OP_JUMP,
+
+ //-------------------
+
+ OP_EQ,
+ OP_NE,
+
+ OP_LTI,
+ OP_LEI,
+ OP_GTI,
+ OP_GEI,
+
+ OP_LTU,
+ OP_LEU,
+ OP_GTU,
+ OP_GEU,
+
+ OP_EQF,
+ OP_NEF,
+
+ OP_LTF,
+ OP_LEF,
+ OP_GTF,
+ OP_GEF,
+
+ //-------------------
+
+ OP_LOAD1,
+ OP_LOAD2,
+ OP_LOAD4,
+ OP_STORE1,
+ OP_STORE2,
+ OP_STORE4, // *(stack[top-1]) = stack[yop
+ OP_ARG,
+ OP_BLOCK_COPY,
+
+ //-------------------
+
+ OP_SEX8,
+ OP_SEX16,
+
+ OP_NEGI,
+ OP_ADD,
+ OP_SUB,
+ OP_DIVI,
+ OP_DIVU,
+ OP_MODI,
+ OP_MODU,
+ OP_MULI,
+ OP_MULU,
+
+ OP_BAND,
+ OP_BOR,
+ OP_BXOR,
+ OP_BCOM,
+
+ OP_LSH,
+ OP_RSHI,
+ OP_RSHU,
+
+ OP_NEGF,
+ OP_ADDF,
+ OP_SUBF,
+ OP_DIVF,
+ OP_MULF,
+
+ OP_CVIF,
+ OP_CVFI
+} opcode_t;
+
+typedef struct {
+ int imageBytes; // after decompression
+ int entryPoint;
+ int stackBase;
+ int stackSize;
+} executableHeader_t;
+
+typedef enum {
+ CODESEG,
+ DATASEG, // initialized 32 bit data, will be byte swapped
+ LITSEG, // strings
+ BSSSEG, // 0 filled
+ JTRGSEG, // pseudo-segment that contains only jump table targets
+ NUM_SEGMENTS
+} segmentName_t;
+
+#define MAX_IMAGE 0x400000
+
+typedef struct {
+ byte image[MAX_IMAGE];
+ int imageUsed;
+ int segmentBase; // only valid on second pass
+} segment_t;
+
+typedef struct symbol_s {
+ struct symbol_s *next;
+ int hash;
+ segment_t *segment;
+ char *name;
+ int value;
+} symbol_t;
+
+typedef struct hashchain_s {
+ void *data;
+ struct hashchain_s *next;
+} hashchain_t;
+
+typedef struct hashtable_s {
+ int buckets;
+ hashchain_t **table;
+} hashtable_t;
+
+int symtablelen = DEFAULT_HASHTABLE_SIZE;
+hashtable_t *symtable;
+hashtable_t *optable;
+
+segment_t segment[NUM_SEGMENTS];
+segment_t *currentSegment;
+
+int passNumber;
+
+int numSymbols;
+int errorCount;
+
+typedef struct options_s {
+ qboolean verbose;
+ qboolean writeMapFile;
+ qboolean vanillaQ3Compatibility;
+} options_t;
+
+options_t options = { 0 };
+
+symbol_t *symbols;
+symbol_t *lastSymbol = 0; /* Most recent symbol defined. */
+
+
+#define MAX_ASM_FILES 256
+int numAsmFiles;
+char *asmFiles[MAX_ASM_FILES];
+char *asmFileNames[MAX_ASM_FILES];
+
+int currentFileIndex;
+char *currentFileName;
+int currentFileLine;
+
+//int stackSize = 16384;
+int stackSize = 0x10000;
+
+// we need to convert arg and ret instructions to
+// stores to the local stack frame, so we need to track the
+// characteristics of the current functions stack frame
+int currentLocals; // bytes of locals needed by this function
+int currentArgs; // bytes of largest argument list called from this function
+int currentArgOffset; // byte offset in currentArgs to store next arg, reset each call
+
+#define MAX_LINE_LENGTH 1024
+char lineBuffer[MAX_LINE_LENGTH];
+int lineParseOffset;
+char token[MAX_LINE_LENGTH];
+
+int instructionCount;
+
+typedef struct {
+ char *name;
+ int opcode;
+} sourceOps_t;
+
+sourceOps_t sourceOps[] = {
+#include "opstrings.h"
+};
+
+#define NUM_SOURCE_OPS ( sizeof( sourceOps ) / sizeof( sourceOps[0] ) )
+
+int opcodesHash[ NUM_SOURCE_OPS ];
+
+
+
+static int vreport (const char* fmt, va_list vp)
+{
+ if (options.verbose != qtrue)
+ return 0;
+ return vprintf(fmt, vp);
+}
+
+static int report (const char *fmt, ...)
+{
+ va_list va;
+ int retval;
+
+ va_start(va, fmt);
+ retval = vreport(fmt, va);
+ va_end(va);
+ return retval;
+}
+
+/* The chain-and-bucket hash table. -PH */
+
+static void hashtable_init (hashtable_t *H, int buckets)
+{
+ H->buckets = buckets;
+ H->table = calloc(H->buckets, sizeof(*(H->table)));
+}
+
+static hashtable_t *hashtable_new (int buckets)
+{
+ hashtable_t *H;
+
+ H = malloc(sizeof(hashtable_t));
+ hashtable_init(H, buckets);
+ return H;
+}
+
+/* No destroy/destructor. No need. */
+
+static void hashtable_add (hashtable_t *H, int hashvalue, void *datum)
+{
+ hashchain_t *hc, **hb;
+
+ hashvalue = (abs(hashvalue) % H->buckets);
+ hb = &(H->table[hashvalue]);
+ if (*hb == 0)
+ {
+ /* Empty bucket. Create new one. */
+ *hb = calloc(1, sizeof(**hb));
+ hc = *hb;
+ }
+ else
+ {
+ /* Get hc to point to last node in chain. */
+ for (hc = *hb; hc && hc->next; hc = hc->next);
+ hc->next = calloc(1, sizeof(*hc));
+ hc = hc->next;
+ }
+ hc->data = datum;
+ hc->next = 0;
+}
+
+static hashchain_t *hashtable_get (hashtable_t *H, int hashvalue)
+{
+ hashvalue = (abs(hashvalue) % H->buckets);
+ return (H->table[hashvalue]);
+}
+
+static void hashtable_stats (hashtable_t *H)
+{
+ int len, empties, longest, nodes;
+ int i;
+ float meanlen;
+ hashchain_t *hc;
+
+ report("Stats for hashtable %08X", H);
+ empties = 0;
+ longest = 0;
+ nodes = 0;
+ for (i = 0; i < H->buckets; i++)
+ {
+ if (H->table[i] == 0)
+ { empties++; continue; }
+ for (hc = H->table[i], len = 0; hc; hc = hc->next, len++);
+ if (len > longest) { longest = len; }
+ nodes += len;
+ }
+ meanlen = (float)(nodes) / (H->buckets - empties);
+#if 0
+/* Long stats display */
+ report(" Total buckets: %d\n", H->buckets);
+ report(" Total stored nodes: %d\n", nodes);
+ report(" Longest chain: %d\n", longest);
+ report(" Empty chains: %d\n", empties);
+ report(" Mean non-empty chain length: %f\n", meanlen);
+#else //0
+/* Short stats display */
+ report(", %d buckets, %d nodes", H->buckets, nodes);
+ report("\n");
+ report(" Longest chain: %d, empty chains: %d, mean non-empty: %f", longest, empties, meanlen);
+#endif //0
+ report("\n");
+}
+
+
+/* Kludge. */
+/* Check if symbol already exists. */
+/* Returns 0 if symbol does NOT already exist, non-zero otherwise. */
+static int hashtable_symbol_exists (hashtable_t *H, int hash, char *sym)
+{
+ hashchain_t *hc;
+ symbol_t *s;
+
+ hash = (abs(hash) % H->buckets);
+ hc = H->table[hash];
+ if (hc == 0)
+ {
+ /* Empty chain means this symbol has not yet been defined. */
+ return 0;
+ }
+ for (; hc; hc = hc->next)
+ {
+ s = (symbol_t*)hc->data;
+// if ((hash == s->hash) && (strcmp(sym, s->name) == 0))
+/* We _already_ know the hash is the same. That's why we're probing! */
+ if (strcmp(sym, s->name) == 0)
+ {
+ /* Symbol collisions -- symbol already exists. */
+ return 1;
+ }
+ }
+ return 0; /* Can't find collision. */
+}
+
+
+
+
+/* Comparator function for quicksorting. */
+static int symlist_cmp (const void *e1, const void *e2)
+{
+ const symbol_t *a, *b;
+
+ a = *(const symbol_t **)e1;
+ b = *(const symbol_t **)e2;
+//crumb("Symbol comparison (1) %d to (2) %d\n", a->value, b->value);
+ return ( a->value - b->value);
+}
+
+/*
+ Sort the symbols list by using QuickSort (qsort()).
+ This may take a LOT of memory (a few megabytes?), but memory is cheap these days.
+ However, qsort(3) already exists, and I'm really lazy.
+ -PH
+*/
+static void sort_symbols ()
+{
+ int i, elems;
+ symbol_t *s;
+ symbol_t **symlist;
+
+ if(!symbols)
+ return;
+
+//crumb("sort_symbols: Constructing symlist array\n");
+ for (elems = 0, s = symbols; s; s = s->next, elems++) /* nop */ ;
+
+ symlist = malloc(elems * sizeof(symbol_t*));
+ for (i = 0, s = symbols; s; s = s->next, i++)
+ {
+ symlist[i] = s;
+ }
+//crumbf("sort_symbols: Quick-sorting %d symbols\n", elems);
+ qsort(symlist, elems, sizeof(symbol_t*), symlist_cmp);
+//crumbf("sort_symbols: Reconstructing symbols list\n");
+ s = symbols = symlist[0];
+ for (i = 1; i < elems; i++)
+ {
+ s->next = symlist[i];
+ s = s->next;
+ }
+ lastSymbol = s;
+ s->next = 0;
+//crumbf("sort_symbols: verifying..."); fflush(stdout);
+ for (i = 0, s = symbols; s; s = s->next, i++) /*nop*/ ;
+//crumbf(" %d elements\n", i);
+ free(symlist); /* d'oh. no gc. */
+}
+
+
+#ifdef _MSC_VER
+#define INT64 __int64
+#define atoi64 _atoi64
+#else
+#define INT64 long long int
+#define atoi64 atoll
+#endif
+
+/*
+ Problem:
+ BYTE values are specified as signed decimal string. A properly functional
+ atoip() will cap large signed values at 0x7FFFFFFF. Negative word values are
+ often specified as very large decimal values by lcc. Therefore, values that
+ should be between 0x7FFFFFFF and 0xFFFFFFFF come out as 0x7FFFFFFF when using
+ atoi(). Bad.
+
+ This function is one big evil hack to work around this problem.
+*/
+static int atoiNoCap (const char *s)
+{
+ INT64 l;
+ union {
+ unsigned int u;
+ signed int i;
+ } retval;
+
+ l = atoi64(s);
+ /* Now smash to signed 32 bits accordingly. */
+ if (l < 0) {
+ retval.i = (int)l;
+ } else {
+ retval.u = (unsigned int)l;
+ }
+ return retval.i; /* <- union hackage. I feel dirty with this. -PH */
+}
+
+
+
+/*
+=============
+HashString
+=============
+*/
+/* Default hash function of Kazlib 1.19, slightly modified. */
+static unsigned int HashString (const char *key)
+{
+ static unsigned long randbox[] = {
+ 0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U,
+ 0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU,
+ 0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU,
+ 0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU,
+ };
+
+ const char *str = key;
+ unsigned int acc = 0;
+
+ while (*str) {
+ acc ^= randbox[(*str + acc) & 0xf];
+ acc = (acc << 1) | (acc >> 31);
+ acc &= 0xffffffffU;
+ acc ^= randbox[((*str++ >> 4) + acc) & 0xf];
+ acc = (acc << 2) | (acc >> 30);
+ acc &= 0xffffffffU;
+ }
+ return abs((int)acc);
+}
+
+
+/*
+============
+CodeError
+============
+*/
+static void CodeError( char *fmt, ... ) {
+ va_list argptr;
+
+ errorCount++;
+
+ fprintf( stderr, "%s:%i ", currentFileName, currentFileLine );
+
+ va_start( argptr,fmt );
+ vfprintf( stderr, fmt, argptr );
+ va_end( argptr );
+}
+
+/*
+============
+EmitByte
+============
+*/
+static void EmitByte( segment_t *seg, int v ) {
+ if ( seg->imageUsed >= MAX_IMAGE ) {
+ Error( "MAX_IMAGE" );
+ }
+ seg->image[ seg->imageUsed ] = v;
+ seg->imageUsed++;
+}
+
+/*
+============
+EmitInt
+============
+*/
+static void EmitInt( segment_t *seg, int v ) {
+ if ( seg->imageUsed >= MAX_IMAGE - 4) {
+ Error( "MAX_IMAGE" );
+ }
+ seg->image[ seg->imageUsed ] = v & 255;
+ seg->image[ seg->imageUsed + 1 ] = ( v >> 8 ) & 255;
+ seg->image[ seg->imageUsed + 2 ] = ( v >> 16 ) & 255;
+ seg->image[ seg->imageUsed + 3 ] = ( v >> 24 ) & 255;
+ seg->imageUsed += 4;
+}
+
+/*
+============
+DefineSymbol
+
+Symbols can only be defined on pass 0
+============
+*/
+static void DefineSymbol( char *sym, int value ) {
+ /* Hand optimization by PhaethonH */
+ symbol_t *s;
+ char expanded[MAX_LINE_LENGTH];
+ int hash;
+
+ if ( passNumber == 1 ) {
+ return;
+ }
+
+ // add the file prefix to local symbols to guarantee unique
+ if ( sym[0] == '$' ) {
+ sprintf( expanded, "%s_%i", sym, currentFileIndex );
+ sym = expanded;
+ }
+
+ hash = HashString( sym );
+
+ if (hashtable_symbol_exists(symtable, hash, sym)) {
+ CodeError( "Multiple definitions for %s\n", sym );
+ return;
+ }
+
+ s = malloc( sizeof( *s ) );
+ s->next = NULL;
+ s->name = copystring( sym );
+ s->hash = hash;
+ s->value = value;
+ s->segment = currentSegment;
+
+ hashtable_add(symtable, hash, s);
+
+/*
+ Hash table lookup already speeds up symbol lookup enormously.
+ We postpone sorting until end of pass 0.
+ Since we're not doing the insertion sort, lastSymbol should always
+ wind up pointing to the end of list.
+ This allows constant time for adding to the list.
+ -PH
+*/
+ if (symbols == 0) {
+ lastSymbol = symbols = s;
+ } else {
+ lastSymbol->next = s;
+ lastSymbol = s;
+ }
+}
+
+
+/*
+============
+LookupSymbol
+
+Symbols can only be evaluated on pass 1
+============
+*/
+static int LookupSymbol( char *sym ) {
+ symbol_t *s;
+ char expanded[MAX_LINE_LENGTH];
+ int hash;
+ hashchain_t *hc;
+
+ if ( passNumber == 0 ) {
+ return 0;
+ }
+
+ // add the file prefix to local symbols to guarantee unique
+ if ( sym[0] == '$' ) {
+ sprintf( expanded, "%s_%i", sym, currentFileIndex );
+ sym = expanded;
+ }
+
+ hash = HashString( sym );
+
+/*
+ Hand optimization by PhaethonH
+
+ Using a hash table with chain/bucket for lookups alone sped up q3asm by almost 3x for me.
+ -PH
+*/
+ for (hc = hashtable_get(symtable, hash); hc; hc = hc->next) {
+ s = (symbol_t*)hc->data; /* ugly typecasting, but it's fast! */
+ if ( (hash == s->hash) && !strcmp(sym, s->name) ) {
+ return s->segment->segmentBase + s->value;
+ }
+ }
+
+ CodeError( "error: symbol %s undefined\n", sym );
+ passNumber = 0;
+ DefineSymbol( sym, 0 ); // so more errors aren't printed
+ passNumber = 1;
+ return 0;
+}
+
+
+/*
+==============
+ExtractLine
+
+Extracts the next line from the given text block.
+If a full line isn't parsed, returns NULL
+Otherwise returns the updated parse pointer
+===============
+*/
+static char *ExtractLine( char *data ) {
+/* Goal:
+ Given a string `data', extract one text line into buffer `lineBuffer' that
+ is no longer than MAX_LINE_LENGTH characters long. Return value is
+ remainder of `data' that isn't part of `lineBuffer'.
+ -PH
+*/
+ /* Hand-optimized by PhaethonH */
+ char *p, *q;
+
+ currentFileLine++;
+
+ lineParseOffset = 0;
+ token[0] = 0;
+ *lineBuffer = 0;
+
+ p = q = data;
+ if (!*q) {
+ return NULL;
+ }
+
+ for ( ; !((*p == 0) || (*p == '\n')); p++) /* nop */ ;
+
+ if ((p - q) >= MAX_LINE_LENGTH) {
+ CodeError( "MAX_LINE_LENGTH" );
+ return data;
+ }
+
+ memcpy( lineBuffer, data, (p - data) );
+ lineBuffer[(p - data)] = 0;
+ p += (*p == '\n') ? 1 : 0; /* Skip over final newline. */
+ return p;
+}
+
+
+/*
+==============
+Parse
+
+Parse a token out of linebuffer
+==============
+*/
+static qboolean Parse( void ) {
+ /* Hand-optimized by PhaethonH */
+ const char *p, *q;
+
+ /* Because lineParseOffset is only updated just before exit, this makes this code version somewhat harder to debug under a symbolic debugger. */
+
+ *token = 0; /* Clear token. */
+
+ // skip whitespace
+ for (p = lineBuffer + lineParseOffset; *p && (*p <= ' '); p++) /* nop */ ;
+
+ // skip ; comments
+ /* die on end-of-string */
+ if ((*p == ';') || (*p == 0)) {
+ lineParseOffset = p - lineBuffer;
+ return qfalse;
+ }
+
+ q = p; /* Mark the start of token. */
+ /* Find separator first. */
+ for ( ; *p > 32; p++) /* nop */ ; /* XXX: unsafe assumptions. */
+ /* *p now sits on separator. Mangle other values accordingly. */
+ strncpy(token, q, p - q);
+ token[p - q] = 0;
+
+ lineParseOffset = p - lineBuffer;
+
+ return qtrue;
+}
+
+
+/*
+==============
+ParseValue
+==============
+*/
+static int ParseValue( void ) {
+ Parse();
+ return atoiNoCap( token );
+}
+
+
+/*
+==============
+ParseExpression
+==============
+*/
+static int ParseExpression(void) {
+ /* Hand optimization, PhaethonH */
+ int i, j;
+ char sym[MAX_LINE_LENGTH];
+ int v;
+
+ /* Skip over a leading minus. */
+ for ( i = ((token[0] == '-') ? 1 : 0) ; i < MAX_LINE_LENGTH ; i++ ) {
+ if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) {
+ break;
+ }
+ }
+
+ memcpy( sym, token, i );
+ sym[i] = 0;
+
+ switch (*sym) { /* Resolve depending on first character. */
+/* Optimizing compilers can convert cases into "calculated jumps". I think these are faster. -PH */
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ v = atoiNoCap(sym);
+ break;
+ default:
+ v = LookupSymbol(sym);
+ break;
+ }
+
+ // parse add / subtract offsets
+ while ( token[i] != 0 ) {
+ for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) {
+ if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) {
+ break;
+ }
+ }
+
+ memcpy( sym, token+i+1, j-i-1 );
+ sym[j-i-1] = 0;
+
+ switch (token[i]) {
+ case '+':
+ v += atoiNoCap(sym);
+ break;
+ case '-':
+ v -= atoiNoCap(sym);
+ break;
+ }
+
+ i = j;
+ }
+
+ return v;
+}
+
+
+/*
+==============
+HackToSegment
+
+BIG HACK: I want to put all 32 bit values in the data
+segment so they can be byte swapped, and all char data in the lit
+segment, but switch jump tables are emited in the lit segment and
+initialized strng variables are put in the data segment.
+
+I can change segments here, but I also need to fixup the
+label that was just defined
+
+Note that the lit segment is read-write in the VM, so strings
+aren't read only as in some architectures.
+==============
+*/
+static void HackToSegment( segmentName_t seg ) {
+ if ( currentSegment == &segment[seg] ) {
+ return;
+ }
+
+ currentSegment = &segment[seg];
+ if ( passNumber == 0 ) {
+ lastSymbol->segment = currentSegment;
+ lastSymbol->value = currentSegment->imageUsed;
+ }
+}
+
+
+
+
+
+
+
+//#define STAT(L) report("STAT " L "\n");
+#define STAT(L)
+#define ASM(O) int TryAssemble##O ()
+
+
+/*
+ These clauses were moved out from AssembleLine() to allow reordering of if's.
+ An optimizing compiler should reconstruct these back into inline code.
+ -PH
+*/
+
+ // call instructions reset currentArgOffset
+ASM(CALL)
+{
+ if ( !strncmp( token, "CALL", 4 ) ) {
+STAT("CALL");
+ EmitByte( &segment[CODESEG], OP_CALL );
+ instructionCount++;
+ currentArgOffset = 0;
+ return 1;
+ }
+ return 0;
+}
+
+ // arg is converted to a reversed store
+ASM(ARG)
+{
+ if ( !strncmp( token, "ARG", 3 ) ) {
+STAT("ARG");
+ EmitByte( &segment[CODESEG], OP_ARG );
+ instructionCount++;
+ if ( 8 + currentArgOffset >= 256 ) {
+ CodeError( "currentArgOffset >= 256" );
+ return 1;
+ }
+ EmitByte( &segment[CODESEG], 8 + currentArgOffset );
+ currentArgOffset += 4;
+ return 1;
+ }
+ return 0;
+}
+
+ // ret just leaves something on the op stack
+ASM(RET)
+{
+ if ( !strncmp( token, "RET", 3 ) ) {
+STAT("RET");
+ EmitByte( &segment[CODESEG], OP_LEAVE );
+ instructionCount++;
+ EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
+ return 1;
+ }
+ return 0;
+}
+
+ // pop is needed to discard the return value of
+ // a function
+ASM(POP)
+{
+ if ( !strncmp( token, "pop", 3 ) ) {
+STAT("POP");
+ EmitByte( &segment[CODESEG], OP_POP );
+ instructionCount++;
+ return 1;
+ }
+ return 0;
+}
+
+ // address of a parameter is converted to OP_LOCAL
+ASM(ADDRF)
+{
+ int v;
+ if ( !strncmp( token, "ADDRF", 5 ) ) {
+STAT("ADDRF");
+ instructionCount++;
+ Parse();
+ v = ParseExpression();
+ v = 16 + currentArgs + currentLocals + v;
+ EmitByte( &segment[CODESEG], OP_LOCAL );
+ EmitInt( &segment[CODESEG], v );
+ return 1;
+ }
+ return 0;
+}
+
+ // address of a local is converted to OP_LOCAL
+ASM(ADDRL)
+{
+ int v;
+ if ( !strncmp( token, "ADDRL", 5 ) ) {
+STAT("ADDRL");
+ instructionCount++;
+ Parse();
+ v = ParseExpression();
+ v = 8 + currentArgs + v;
+ EmitByte( &segment[CODESEG], OP_LOCAL );
+ EmitInt( &segment[CODESEG], v );
+ return 1;
+ }
+ return 0;
+}
+
+ASM(PROC)
+{
+ char name[1024];
+ if ( !strcmp( token, "proc" ) ) {
+STAT("PROC");
+ Parse(); // function name
+ strcpy( name, token );
+
+ DefineSymbol( token, instructionCount ); // segment[CODESEG].imageUsed );
+
+ currentLocals = ParseValue(); // locals
+ currentLocals = ( currentLocals + 3 ) & ~3;
+ currentArgs = ParseValue(); // arg marshalling
+ currentArgs = ( currentArgs + 3 ) & ~3;
+
+ if ( 8 + currentLocals + currentArgs >= 32767 ) {
+ CodeError( "Locals > 32k in %s\n", name );
+ }
+
+ instructionCount++;
+ EmitByte( &segment[CODESEG], OP_ENTER );
+ EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
+ return 1;
+ }
+ return 0;
+}
+
+
+ASM(ENDPROC)
+{
+ if ( !strcmp( token, "endproc" ) ) {
+STAT("ENDPROC");
+ Parse(); // skip the function name
+ ParseValue(); // locals
+ ParseValue(); // arg marshalling
+
+ // all functions must leave something on the opstack
+ instructionCount++;
+ EmitByte( &segment[CODESEG], OP_PUSH );
+
+ instructionCount++;
+ EmitByte( &segment[CODESEG], OP_LEAVE );
+ EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
+
+ return 1;
+ }
+ return 0;
+}
+
+
+ASM(ADDRESS)
+{
+ int v;
+ if ( !strcmp( token, "address" ) ) {
+STAT("ADDRESS");
+ Parse();
+ v = ParseExpression();
+
+/* Addresses are 32 bits wide, and therefore go into data segment. */
+ HackToSegment( DATASEG );
+ EmitInt( currentSegment, v );
+ if( passNumber == 1 && token[ 0 ] == '$' ) // crude test for labels
+ EmitInt( &segment[ JTRGSEG ], v );
+ return 1;
+ }
+ return 0;
+}
+
+ASM(EXPORT)
+{
+ if ( !strcmp( token, "export" ) ) {
+STAT("EXPORT");
+ return 1;
+ }
+ return 0;
+}
+
+ASM(IMPORT)
+{
+ if ( !strcmp( token, "import" ) ) {
+STAT("IMPORT");
+ return 1;
+ }
+ return 0;
+}
+
+ASM(CODE)
+{
+ if ( !strcmp( token, "code" ) ) {
+STAT("CODE");
+ currentSegment = &segment[CODESEG];
+ return 1;
+ }
+ return 0;
+}
+
+ASM(BSS)
+{
+ if ( !strcmp( token, "bss" ) ) {
+STAT("BSS");
+ currentSegment = &segment[BSSSEG];
+ return 1;
+ }
+ return 0;
+}
+
+ASM(DATA)
+{
+ if ( !strcmp( token, "data" ) ) {
+STAT("DATA");
+ currentSegment = &segment[DATASEG];
+ return 1;
+ }
+ return 0;
+}
+
+ASM(LIT)
+{
+ if ( !strcmp( token, "lit" ) ) {
+STAT("LIT");
+ currentSegment = &segment[LITSEG];
+ return 1;
+ }
+ return 0;
+}
+
+ASM(LINE)
+{
+ if ( !strcmp( token, "line" ) ) {
+STAT("LINE");
+ return 1;
+ }
+ return 0;
+}
+
+ASM(FILE)
+{
+ if ( !strcmp( token, "file" ) ) {
+STAT("FILE");
+ return 1;
+ }
+ return 0;
+}
+
+ASM(EQU)
+{
+ char name[1024];
+ if ( !strcmp( token, "equ" ) ) {
+STAT("EQU");
+ Parse();
+ strcpy( name, token );
+ Parse();
+ DefineSymbol( name, atoiNoCap(token) );
+ return 1;
+ }
+ return 0;
+}
+
+ASM(ALIGN)
+{
+ int v;
+ if ( !strcmp( token, "align" ) ) {
+STAT("ALIGN");
+ v = ParseValue();
+ currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 );
+ return 1;
+ }
+ return 0;
+}
+
+ASM(SKIP)
+{
+ int v;
+ if ( !strcmp( token, "skip" ) ) {
+STAT("SKIP");
+ v = ParseValue();
+ currentSegment->imageUsed += v;
+ return 1;
+ }
+ return 0;
+}
+
+ASM(BYTE)
+{
+ int i, v, v2;
+ if ( !strcmp( token, "byte" ) ) {
+STAT("BYTE");
+ v = ParseValue();
+ v2 = ParseValue();
+
+ if ( v == 1 ) {
+/* Character (1-byte) values go into lit(eral) segment. */
+ HackToSegment( LITSEG );
+ } else if ( v == 4 ) {
+/* 32-bit (4-byte) values go into data segment. */
+ HackToSegment( DATASEG );
+ } else if ( v == 2 ) {
+/* and 16-bit (2-byte) values will cause q3asm to barf. */
+ CodeError( "16 bit initialized data not supported" );
+ }
+
+ // emit little endien
+ for ( i = 0 ; i < v ; i++ ) {
+ EmitByte( currentSegment, (v2 & 0xFF) ); /* paranoid ANDing -PH */
+ v2 >>= 8;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+ // code labels are emited as instruction counts, not byte offsets,
+ // because the physical size of the code will change with
+ // different run time compilers and we want to minimize the
+ // size of the required translation table
+ASM(LABEL)
+{
+ if ( !strncmp( token, "LABEL", 5 ) ) {
+STAT("LABEL");
+ Parse();
+ if ( currentSegment == &segment[CODESEG] ) {
+ DefineSymbol( token, instructionCount );
+ } else {
+ DefineSymbol( token, currentSegment->imageUsed );
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+
+/*
+==============
+AssembleLine
+
+==============
+*/
+static void AssembleLine( void ) {
+ hashchain_t *hc;
+ sourceOps_t *op;
+ int i;
+ int hash;
+
+ Parse();
+ if ( !token[0] ) {
+ return;
+ }
+
+ hash = HashString( token );
+
+/*
+ Opcode search using hash table.
+ Since the opcodes stays mostly fixed, this may benefit even more from a tree.
+ Always with the tree :)
+ -PH
+*/
+ for (hc = hashtable_get(optable, hash); hc; hc = hc->next) {
+ op = (sourceOps_t*)(hc->data);
+ i = op - sourceOps;
+ if ((hash == opcodesHash[i]) && (!strcmp(token, op->name))) {
+ int opcode;
+ int expression;
+
+ if ( op->opcode == OP_UNDEF ) {
+ CodeError( "Undefined opcode: %s\n", token );
+ }
+ if ( op->opcode == OP_IGNORE ) {
+ return; // we ignore most conversions
+ }
+
+ // sign extensions need to check next parm
+ opcode = op->opcode;
+ if ( opcode == OP_SEX8 ) {
+ Parse();
+ if ( token[0] == '1' ) {
+ opcode = OP_SEX8;
+ } else if ( token[0] == '2' ) {
+ opcode = OP_SEX16;
+ } else {
+ CodeError( "Bad sign extension: %s\n", token );
+ return;
+ }
+ }
+
+ // check for expression
+ Parse();
+ if ( token[0] && op->opcode != OP_CVIF
+ && op->opcode != OP_CVFI ) {
+ expression = ParseExpression();
+
+ // code like this can generate non-dword block copies:
+ // auto char buf[2] = " ";
+ // we are just going to round up. This might conceivably
+ // be incorrect if other initialized chars follow.
+ if ( opcode == OP_BLOCK_COPY ) {
+ expression = ( expression + 3 ) & ~3;
+ }
+
+ EmitByte( &segment[CODESEG], opcode );
+ EmitInt( &segment[CODESEG], expression );
+ } else {
+ EmitByte( &segment[CODESEG], opcode );
+ }
+
+ instructionCount++;
+ return;
+ }
+ }
+
+/* This falls through if an assembly opcode is not found. -PH */
+
+/* The following should be sorted in sequence of statistical frequency, most frequent first. -PH */
+/*
+Empirical frequency statistics from FI 2001.01.23:
+ 109892 STAT ADDRL
+ 72188 STAT BYTE
+ 51150 STAT LINE
+ 50906 STAT ARG
+ 43704 STAT IMPORT
+ 34902 STAT LABEL
+ 32066 STAT ADDRF
+ 23704 STAT CALL
+ 7720 STAT POP
+ 7256 STAT RET
+ 5198 STAT ALIGN
+ 3292 STAT EXPORT
+ 2878 STAT PROC
+ 2878 STAT ENDPROC
+ 2812 STAT ADDRESS
+ 738 STAT SKIP
+ 374 STAT EQU
+ 280 STAT CODE
+ 176 STAT LIT
+ 102 STAT FILE
+ 100 STAT BSS
+ 68 STAT DATA
+
+ -PH
+*/
+
+#undef ASM
+#define ASM(O) if (TryAssemble##O ()) return;
+
+ ASM(ADDRL)
+ ASM(BYTE)
+ ASM(LINE)
+ ASM(ARG)
+ ASM(IMPORT)
+ ASM(LABEL)
+ ASM(ADDRF)
+ ASM(CALL)
+ ASM(POP)
+ ASM(RET)
+ ASM(ALIGN)
+ ASM(EXPORT)
+ ASM(PROC)
+ ASM(ENDPROC)
+ ASM(ADDRESS)
+ ASM(SKIP)
+ ASM(EQU)
+ ASM(CODE)
+ ASM(LIT)
+ ASM(FILE)
+ ASM(BSS)
+ ASM(DATA)
+
+ CodeError( "Unknown token: %s\n", token );
+}
+
+/*
+==============
+InitTables
+==============
+*/
+void InitTables( void ) {
+ int i;
+
+ symtable = hashtable_new(symtablelen);
+ optable = hashtable_new(100); /* There's hardly 100 opcodes anyway. */
+
+ for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
+ opcodesHash[i] = HashString( sourceOps[i].name );
+ hashtable_add(optable, opcodesHash[i], sourceOps + i);
+ }
+}
+
+
+/*
+==============
+WriteMapFile
+==============
+*/
+static void WriteMapFile( void ) {
+ FILE *f;
+ symbol_t *s;
+ char imageName[MAX_OS_PATH];
+ int seg;
+
+ strcpy( imageName, outputFilename );
+ StripExtension( imageName );
+ strcat( imageName, ".map" );
+
+ report( "Writing %s...\n", imageName );
+
+ f = SafeOpenWrite( imageName );
+ for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) {
+ for ( s = symbols ; s ; s = s->next ) {
+ if ( s->name[0] == '$' ) {
+ continue; // skip locals
+ }
+ if ( &segment[seg] != s->segment ) {
+ continue;
+ }
+ fprintf( f, "%i %8x %s\n", seg, s->value, s->name );
+ }
+ }
+ fclose( f );
+}
+
+/*
+===============
+WriteVmFile
+===============
+*/
+static void WriteVmFile( void ) {
+ char imageName[MAX_OS_PATH];
+ vmHeader_t header;
+ FILE *f;
+ int headerSize;
+
+ report( "%i total errors\n", errorCount );
+
+ strcpy( imageName, outputFilename );
+ StripExtension( imageName );
+ strcat( imageName, ".qvm" );
+
+ remove( imageName );
+
+ report( "code segment: %7i\n", segment[CODESEG].imageUsed );
+ report( "data segment: %7i\n", segment[DATASEG].imageUsed );
+ report( "lit segment: %7i\n", segment[LITSEG].imageUsed );
+ report( "bss segment: %7i\n", segment[BSSSEG].imageUsed );
+ report( "instruction count: %i\n", instructionCount );
+
+ if ( errorCount != 0 ) {
+ report( "Not writing a file due to errors\n" );
+ return;
+ }
+
+ if( !options.vanillaQ3Compatibility ) {
+ header.vmMagic = VM_MAGIC_VER2;
+ headerSize = sizeof( header );
+ } else {
+ header.vmMagic = VM_MAGIC;
+
+ // Don't write the VM_MAGIC_VER2 bits when maintaining 1.32b compatibility.
+ // (I know this isn't strictly correct due to padding, but then platforms
+ // that pad wouldn't be able to write a correct header anyway). Note: if
+ // vmHeader_t changes, this needs to be adjusted too.
+ headerSize = sizeof( header ) - sizeof( header.jtrgLength );
+ }
+
+ header.instructionCount = instructionCount;
+ header.codeOffset = headerSize;
+ header.codeLength = segment[CODESEG].imageUsed;
+ header.dataOffset = header.codeOffset + segment[CODESEG].imageUsed;
+ header.dataLength = segment[DATASEG].imageUsed;
+ header.litLength = segment[LITSEG].imageUsed;
+ header.bssLength = segment[BSSSEG].imageUsed;
+ header.jtrgLength = segment[JTRGSEG].imageUsed;
+
+ report( "Writing to %s\n", imageName );
+
+#ifdef Q3_BIG_ENDIAN
+ {
+ int i;
+
+ // byte swap the header
+ for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
+ ((int *)&header)[i] = LittleLong( ((int *)&header)[i] );
+ }
+ }
+#endif
+
+ CreatePath( imageName );
+ f = SafeOpenWrite( imageName );
+ SafeWrite( f, &header, headerSize );
+ SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed );
+ SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed );
+ SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed );
+
+ if( !options.vanillaQ3Compatibility ) {
+ SafeWrite( f, &segment[JTRGSEG].image, segment[JTRGSEG].imageUsed );
+ }
+
+ fclose( f );
+}
+
+/*
+===============
+Assemble
+===============
+*/
+static void Assemble( void ) {
+ int i;
+ char filename[MAX_OS_PATH];
+ char *ptr;
+
+ report( "outputFilename: %s\n", outputFilename );
+
+ for ( i = 0 ; i < numAsmFiles ; i++ ) {
+ strcpy( filename, asmFileNames[ i ] );
+ DefaultExtension( filename, ".asm" );
+ LoadFile( filename, (void **)&asmFiles[i] );
+ }
+
+ // assemble
+ for ( passNumber = 0 ; passNumber < 2 ; passNumber++ ) {
+ segment[LITSEG].segmentBase = segment[DATASEG].imageUsed;
+ segment[BSSSEG].segmentBase = segment[LITSEG].segmentBase + segment[LITSEG].imageUsed;
+ segment[JTRGSEG].segmentBase = segment[BSSSEG].segmentBase + segment[BSSSEG].imageUsed;
+ for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
+ segment[i].imageUsed = 0;
+ }
+ segment[DATASEG].imageUsed = 4; // skip the 0 byte, so NULL pointers are fixed up properly
+ instructionCount = 0;
+
+ for ( i = 0 ; i < numAsmFiles ; i++ ) {
+ currentFileIndex = i;
+ currentFileName = asmFileNames[ i ];
+ currentFileLine = 0;
+ report("pass %i: %s\n", passNumber, currentFileName );
+ fflush( NULL );
+ ptr = asmFiles[i];
+ while ( ptr ) {
+ ptr = ExtractLine( ptr );
+ AssembleLine();
+ }
+ }
+
+ // align all segment
+ for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
+ segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3;
+ }
+ if (passNumber == 0) {
+ sort_symbols();
+ }
+ }
+
+ // reserve the stack in bss
+ DefineSymbol( "_stackStart", segment[BSSSEG].imageUsed );
+ segment[BSSSEG].imageUsed += stackSize;
+ DefineSymbol( "_stackEnd", segment[BSSSEG].imageUsed );
+
+ // write the image
+ WriteVmFile();
+
+ // write the map file even if there were errors
+ if( options.writeMapFile ) {
+ WriteMapFile();
+ }
+}
+
+
+/*
+=============
+ParseOptionFile
+
+=============
+*/
+static void ParseOptionFile( const char *filename ) {
+ char expanded[MAX_OS_PATH];
+ char *text, *text_p;
+
+ strcpy( expanded, filename );
+ DefaultExtension( expanded, ".q3asm" );
+ LoadFile( expanded, (void **)&text );
+ if ( !text ) {
+ return;
+ }
+
+ text_p = text;
+
+ while( ( text_p = COM_Parse( text_p ) ) != 0 ) {
+ if ( !strcmp( com_token, "-o" ) ) {
+ // allow output override in option file
+ text_p = COM_Parse( text_p );
+ if ( text_p ) {
+ strcpy( outputFilename, com_token );
+ }
+ continue;
+ }
+
+ asmFileNames[ numAsmFiles ] = copystring( com_token );
+ numAsmFiles++;
+ }
+}
+
+static void ShowHelp( char *argv0 ) {
+ Error("Usage: %s [OPTION]... [FILES]...\n\
+Assemble LCC bytecode assembly to Q3VM bytecode.\n\
+\n\
+ -o OUTPUT Write assembled output to file OUTPUT.qvm\n\
+ -f LISTFILE Read options and list of files to assemble from LISTFILE.q3asm\n\
+ -b BUCKETS Set symbol hash table to BUCKETS buckets\n\
+ -m Generate a mapfile for each OUTPUT.qvm\n\
+ -v Verbose compilation report\n\
+ -vq3 Produce a qvm file compatible with Q3 1.32b\n\
+ -h --help -? Show this help\n\
+", argv0);
+}
+
+/*
+==============
+main
+==============
+*/
+int main( int argc, char **argv ) {
+ int i;
+ double start, end;
+
+// _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler
+
+ if ( argc < 2 ) {
+ ShowHelp( argv[0] );
+ }
+
+ start = I_FloatTime ();
+
+ // default filename is "q3asm"
+ strcpy( outputFilename, "q3asm" );
+ numAsmFiles = 0;
+
+ for ( i = 1 ; i < argc ; i++ ) {
+ if ( argv[i][0] != '-' ) {
+ break;
+ }
+ if( !strcmp( argv[ i ], "-h" ) ||
+ !strcmp( argv[ i ], "--help" ) ||
+ !strcmp( argv[ i ], "-?") ) {
+ ShowHelp( argv[0] );
+ }
+
+ if ( !strcmp( argv[i], "-o" ) ) {
+ if ( i == argc - 1 ) {
+ Error( "-o must preceed a filename" );
+ }
+/* Timbo of Tremulous pointed out -o not working; stock ID q3asm folded in the change. Yay. */
+ strcpy( outputFilename, argv[ i+1 ] );
+ i++;
+ continue;
+ }
+
+ if ( !strcmp( argv[i], "-f" ) ) {
+ if ( i == argc - 1 ) {
+ Error( "-f must preceed a filename" );
+ }
+ ParseOptionFile( argv[ i+1 ] );
+ i++;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-b")) {
+ if (i == argc - 1) {
+ Error("-b requires an argument");
+ }
+ i++;
+ symtablelen = atoiNoCap(argv[i]);
+ continue;
+ }
+
+ if( !strcmp( argv[ i ], "-v" ) ) {
+/* Verbosity option added by Timbo, 2002.09.14.
+By default (no -v option), q3asm remains silent except for critical errors.
+Verbosity turns on all messages, error or not.
+Motivation: not wanting to scrollback for pages to find asm error.
+*/
+ options.verbose = qtrue;
+ continue;
+ }
+
+ if( !strcmp( argv[ i ], "-m" ) ) {
+ options.writeMapFile = qtrue;
+ continue;
+ }
+
+ if( !strcmp( argv[ i ], "-vq3" ) ) {
+ options.vanillaQ3Compatibility = qtrue;
+ continue;
+ }
+
+ Error( "Unknown option: %s", argv[i] );
+ }
+
+ // the rest of the command line args are asm files
+ for ( ; i < argc ; i++ ) {
+ asmFileNames[ numAsmFiles ] = copystring( argv[ i ] );
+ numAsmFiles++;
+ }
+ // In some case it Segfault without this check
+ if ( numAsmFiles == 0 ) {
+ Error( "No file to assemble" );
+ }
+
+ InitTables();
+ Assemble();
+
+ {
+ symbol_t *s;
+
+ for ( i = 0, s = symbols ; s ; s = s->next, i++ ) /* nop */ ;
+
+ if (options.verbose)
+ {
+ report("%d symbols defined\n", i);
+ hashtable_stats(symtable);
+ hashtable_stats(optable);
+ }
+ }
+
+ end = I_FloatTime ();
+ report ("%5.0f seconds elapsed\n", end-start);
+
+ return errorCount;
+}
+
diff --git a/src/tools/lcc/COPYRIGHT b/src/tools/lcc/COPYRIGHT
new file mode 100644
index 0000000..961a48f
--- /dev/null
+++ b/src/tools/lcc/COPYRIGHT
@@ -0,0 +1,61 @@
+The authors of this software are Christopher W. Fraser and
+David R. Hanson.
+
+Copyright (c) 1991,1992,1993,1994,1995,1996,1997,1998 by AT&T,
+Christopher W. Fraser, and David R. Hanson. All Rights Reserved.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose, subject to the provisions described below, without fee is
+hereby granted, provided that this entire notice is included in all
+copies of any software that is or includes a copy or modification of
+this software and in all copies of the supporting documentation for
+such software.
+
+THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
+REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+
+
+lcc is not public-domain software, shareware, and it is not protected
+by a `copyleft' agreement, like the code from the Free Software
+Foundation.
+
+lcc is available free for your personal research and instructional use
+under the `fair use' provisions of the copyright law. You may, however,
+redistribute lcc in whole or in part provided you acknowledge its
+source and include this CPYRIGHT file. You may, for example, include
+the distribution in a CDROM of free software, provided you charge only
+for the media, or mirror the distribution files at your site.
+
+You may not sell lcc or any product derived from it in which it is a
+significant part of the value of the product. Using the lcc front end
+to build a C syntax checker is an example of this kind of product.
+
+You may use parts of lcc in products as long as you charge for only
+those components that are entirely your own and you acknowledge the use
+of lcc clearly in all product documentation and distribution media. You
+must state clearly that your product uses or is based on parts of lcc
+and that lcc is available free of charge. You must also request that
+bug reports on your product be reported to you. Using the lcc front
+end to build a C compiler for the Motorola 88000 chip and charging for
+and distributing only the 88000 code generator is an example of this
+kind of product.
+
+Using parts of lcc in other products is more problematic. For example,
+using parts of lcc in a C++ compiler could save substantial time and
+effort and therefore contribute significantly to the profitability of
+the product. This kind of use, or any use where others stand to make a
+profit from what is primarily our work, requires a license agreement
+with Addison-Wesley. Per-copy and unlimited use licenses are
+available; for more information, contact
+
+ J. Carter Shanklin
+ Addison Wesley Longman, Inc.
+ 2725 Sand Hill Rd.
+ Menlo Park, CA 94025
+ 650/854-0300 x2478 FAX: 650/614-2930 jcs@awl.com
+-----
+Chris Fraser / cwfraser@microsoft.com
+David Hanson / drh@microsoft.com
+$Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $
diff --git a/src/tools/lcc/LOG b/src/tools/lcc/LOG
new file mode 100644
index 0000000..dd23f62
--- /dev/null
+++ b/src/tools/lcc/LOG
@@ -0,0 +1,91 @@
+From lcc 4.0 to 4.1:
+
+Changes:
+
+See doc/4.html for changes in the code-generation interface.
+
+Warns about constants that are too large, eg, short x = 70000;
+
+Warns about expressions that have no effect.
+
+Unsigned shorts are now used for wide-character constants, and
+wchar_t is a typedef for unsigned short.
+
+More assertions in gen.c to confirm that the register allocator is
+configured correctly; ie, that the various masks, wildcards,
+clobbers, and targets are internally consistent. Full checking
+appears impractical, but there's still more than than there was
+before.
+
+On the SPARC, lcc now emits .type and .size directives
+unconditionally.
+
+On the x86, constants are now emitted into the text segment.
+
+If the environment variable "LCCDIR" is defined, it gives the directory
+that contains the preprocessor, the compiler proper, and the
+lcc-specific libraries.
+
+Under Windows, lcc searches the directories named in the environment
+variable "include" for header files.
+
+Errors fixed:
+
+Erroneously complained about unknown sizes for some const fields, eg,
+typedef struct foo ref; struct foo { const ref *q; int a; };
+f(ref *p, int i) { return p->q[i].a; }
+
+-A -A erroneously complained about static main's that didn't conform
+to the ANSI-mandated "int main(void)" or "int main(int, char **)".
+
+Silently generated incorrect code for a structure copy with a
+post-incremented target, eg,
+struct { int x; } data = {1}, copy[2], *q = copy;
+main() { *q++ = data; }
+
+Generated incorrect values in some expressions with constant pointers.
+
+Silently truncated string literals longer than 4095 characters.
+
+Failed to emit debugging information for uninitialized globals.
+
+Failed to diagnose missing sizes in some multi-dimensioned array
+declarators, eg, extern int x[][10]; int x[5][];
+
+Silently emitted incorrect sizes and initalizations for some
+incomplete multi-dimensioned arrays involving pointers and whose size
+is determined by the number of initializers.
+
+Set only the x.name field for some back-end symbols (eg, wildcards),
+and the uninitialized name field crashed some debugging output.
+
+uses() failed to check the register *set* as well as the register
+mask. There's no known bug demo, but a wildcard set might be
+contrived that would need the test.
+
+Crashed with -b on some conditional expressions involving calls, eg,
+int p; void g(void) { p ? f() : 1; }
+
+On the MIPS, sometimes generated an incorrect frame size and thus a
+crash when floating-point registers were saved.
+
+On the SPARC, erroneously reused a register variable as a temporary
+when the variable is compiler-generated.
+
+On the SPARC with -b, emitted incorrect code for returning structs.
+
+On the x86, conversion from float to int rounded instead of truncated
+with the default floating-point mode.
+
+On the x86, eliminate rtargets for kids after the first (see p. 419).
+
+On the x86, substitute reg for freg, in order to use the common reg
+rules. Needed only for debugging output, since we're not using any
+float regs as regs at this time.
+
+On the x86, "double f(); main(){f();}" wasn't popping the FP register stack.
+
+On the x86, ECX was saved by the callee, when it should have been
+saved by the caller.
+
+$Id: LOG 145 2001-10-17 21:53:10Z timo $
diff --git a/src/tools/lcc/README b/src/tools/lcc/README
new file mode 100644
index 0000000..4ba4d3f
--- /dev/null
+++ b/src/tools/lcc/README
@@ -0,0 +1,21 @@
+This hierarchy is the distribution for lcc version 4.1.
+
+lcc version 3.x is described in the book "A Retargetable C Compiler:
+Design and Implementation" (Addison-Wesley, 1995, ISBN 0-8053-1670-1).
+There are significant differences between 3.x and 4.x, most notably in
+the intermediate code. doc/4.html summarizes the differences.
+
+VERSION 4.1 IS INCOMPATIBLE WITH EARLIER VERSIONS OF LCC. DO NOT
+UNLOAD THIS DISTRIBUTION ON TOP OF A 3.X DISTRIBUTION.
+
+LOG describes the changes since the last release.
+
+CPYRIGHT describes the conditions under you can use, copy, modify, and
+distribute lcc or works derived from lcc.
+
+doc/install.html is an HTML file that gives a complete description of
+the distribution and installation instructions.
+
+Chris Fraser / cwfraser@microsoft.com
+David Hanson / drh@microsoft.com
+$Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $
diff --git a/src/tools/lcc/README.id b/src/tools/lcc/README.id
new file mode 100644
index 0000000..6611a37
--- /dev/null
+++ b/src/tools/lcc/README.id
@@ -0,0 +1,3 @@
+2001-10-31 Timothee Besset <ttimo@idsoftware.com>
+updated from the $/source/lcc code
+modified for portability and use with >= 1.31 mod source release
diff --git a/src/tools/lcc/cpp/cpp.c b/src/tools/lcc/cpp/cpp.c
new file mode 100644
index 0000000..5c0cfd7
--- /dev/null
+++ b/src/tools/lcc/cpp/cpp.c
@@ -0,0 +1,326 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include "cpp.h"
+
+char rcsid[] = "cpp.c - faked rcsid";
+
+#define OUTS 16384
+char outbuf[OUTS];
+char *outbufp = outbuf;
+Source *cursource;
+int nerrs;
+struct token nltoken = { NL, 0, 0, 0, 1, (uchar*)"\n" };
+char *curtime;
+int incdepth;
+int ifdepth;
+int ifsatisfied[NIF];
+int skipping;
+
+
+int
+main(int argc, char **argv)
+{
+ Tokenrow tr;
+ time_t t;
+ char ebuf[BUFSIZ];
+
+ setbuf(stderr, ebuf);
+ t = time(NULL);
+ curtime = ctime(&t);
+ maketokenrow(3, &tr);
+ expandlex();
+ setup(argc, argv);
+ fixlex();
+ iniths();
+ genline();
+ process(&tr);
+ flushout();
+ fflush(stderr);
+ exit(nerrs > 0);
+ return 0;
+}
+
+void
+process(Tokenrow *trp)
+{
+ int anymacros = 0;
+
+ for (;;) {
+ if (trp->tp >= trp->lp) {
+ trp->tp = trp->lp = trp->bp;
+ outbufp = outbuf;
+ anymacros |= gettokens(trp, 1);
+ trp->tp = trp->bp;
+ }
+ if (trp->tp->type == END) {
+ if (--incdepth>=0) {
+ if (cursource->ifdepth)
+ error(ERROR,
+ "Unterminated conditional in #include");
+ unsetsource();
+ cursource->line += cursource->lineinc;
+ trp->tp = trp->lp;
+ genline();
+ continue;
+ }
+ if (ifdepth)
+ error(ERROR, "Unterminated #if/#ifdef/#ifndef");
+ break;
+ }
+ if (trp->tp->type==SHARP) {
+ trp->tp += 1;
+ control(trp);
+ } else if (!skipping && anymacros)
+ expandrow(trp, NULL);
+ if (skipping)
+ setempty(trp);
+ puttokens(trp);
+ anymacros = 0;
+ cursource->line += cursource->lineinc;
+ if (cursource->lineinc>1) {
+ genline();
+ }
+ }
+}
+
+void
+control(Tokenrow *trp)
+{
+ Nlist *np;
+ Token *tp;
+
+ tp = trp->tp;
+ if (tp->type!=NAME) {
+ if (tp->type==NUMBER)
+ goto kline;
+ if (tp->type != NL)
+ error(ERROR, "Unidentifiable control line");
+ return; /* else empty line */
+ }
+ if ((np = lookup(tp, 0))==NULL || ((np->flag&ISKW)==0 && !skipping)) {
+ error(WARNING, "Unknown preprocessor control %t", tp);
+ return;
+ }
+ if (skipping) {
+ switch (np->val) {
+ case KENDIF:
+ if (--ifdepth<skipping)
+ skipping = 0;
+ --cursource->ifdepth;
+ setempty(trp);
+ return;
+
+ case KIFDEF:
+ case KIFNDEF:
+ case KIF:
+ if (++ifdepth >= NIF)
+ error(FATAL, "#if too deeply nested");
+ ++cursource->ifdepth;
+ return;
+
+ case KELIF:
+ case KELSE:
+ if (ifdepth<=skipping)
+ break;
+ return;
+
+ default:
+ return;
+ }
+ }
+ switch (np->val) {
+ case KDEFINE:
+ dodefine(trp);
+ break;
+
+ case KUNDEF:
+ tp += 1;
+ if (tp->type!=NAME || trp->lp - trp->bp != 4) {
+ error(ERROR, "Syntax error in #undef");
+ break;
+ }
+ if ((np = lookup(tp, 0)) != NULL)
+ np->flag &= ~ISDEFINED;
+ break;
+
+ case KPRAGMA:
+ return;
+
+ case KIFDEF:
+ case KIFNDEF:
+ case KIF:
+ if (++ifdepth >= NIF)
+ error(FATAL, "#if too deeply nested");
+ ++cursource->ifdepth;
+ ifsatisfied[ifdepth] = 0;
+ if (eval(trp, np->val))
+ ifsatisfied[ifdepth] = 1;
+ else
+ skipping = ifdepth;
+ break;
+
+ case KELIF:
+ if (ifdepth==0) {
+ error(ERROR, "#elif with no #if");
+ return;
+ }
+ if (ifsatisfied[ifdepth]==2)
+ error(ERROR, "#elif after #else");
+ if (eval(trp, np->val)) {
+ if (ifsatisfied[ifdepth])
+ skipping = ifdepth;
+ else {
+ skipping = 0;
+ ifsatisfied[ifdepth] = 1;
+ }
+ } else
+ skipping = ifdepth;
+ break;
+
+ case KELSE:
+ if (ifdepth==0 || cursource->ifdepth==0) {
+ error(ERROR, "#else with no #if");
+ return;
+ }
+ if (ifsatisfied[ifdepth]==2)
+ error(ERROR, "#else after #else");
+ if (trp->lp - trp->bp != 3)
+ error(ERROR, "Syntax error in #else");
+ skipping = ifsatisfied[ifdepth]? ifdepth: 0;
+ ifsatisfied[ifdepth] = 2;
+ break;
+
+ case KENDIF:
+ if (ifdepth==0 || cursource->ifdepth==0) {
+ error(ERROR, "#endif with no #if");
+ return;
+ }
+ --ifdepth;
+ --cursource->ifdepth;
+ if (trp->lp - trp->bp != 3)
+ error(WARNING, "Syntax error in #endif");
+ break;
+
+ case KWARNING:
+ trp->tp = tp+1;
+ error(WARNING, "#warning directive: %r", trp);
+ break;
+
+ case KERROR:
+ trp->tp = tp+1;
+ error(ERROR, "#error directive: %r", trp);
+ break;
+
+ case KLINE:
+ trp->tp = tp+1;
+ expandrow(trp, "<line>");
+ tp = trp->bp+2;
+ kline:
+ if (tp+1>=trp->lp || tp->type!=NUMBER || tp+3<trp->lp
+ || ((tp+3==trp->lp && ((tp+1)->type!=STRING))||*(tp+1)->t=='L')){
+ error(ERROR, "Syntax error in #line");
+ return;
+ }
+ cursource->line = atol((char*)tp->t)-1;
+ if (cursource->line<0 || cursource->line>=32768)
+ error(WARNING, "#line specifies number out of range");
+ tp = tp+1;
+ if (tp+1<trp->lp)
+ cursource->filename=(char*)newstring(tp->t+1,tp->len-2,0);
+ return;
+
+ case KDEFINED:
+ error(ERROR, "Bad syntax for control line");
+ break;
+
+ case KINCLUDE:
+ doinclude(trp);
+ trp->lp = trp->bp;
+ return;
+
+ case KEVAL:
+ eval(trp, np->val);
+ break;
+
+ default:
+ error(ERROR, "Preprocessor control `%t' not yet implemented", tp);
+ break;
+ }
+ setempty(trp);
+}
+
+void *
+domalloc(int size)
+{
+ void *p = malloc(size);
+
+ if (p==NULL)
+ error(FATAL, "Out of memory from malloc");
+ return p;
+}
+
+void
+dofree(void *p)
+{
+ free(p);
+}
+
+void
+error(enum errtype type, char *string, ...)
+{
+ va_list ap;
+ char *cp, *ep;
+ Token *tp;
+ Tokenrow *trp;
+ Source *s;
+ int i;
+
+ fprintf(stderr, "cpp: ");
+ for (s=cursource; s; s=s->next)
+ if (*s->filename)
+ fprintf(stderr, "%s:%d ", s->filename, s->line);
+ va_start(ap, string);
+ for (ep=string; *ep; ep++) {
+ if (*ep=='%') {
+ switch (*++ep) {
+
+ case 's':
+ cp = va_arg(ap, char *);
+ fprintf(stderr, "%s", cp);
+ break;
+ case 'd':
+ i = va_arg(ap, int);
+ fprintf(stderr, "%d", i);
+ break;
+ case 't':
+ tp = va_arg(ap, Token *);
+ fprintf(stderr, "%.*s", tp->len, tp->t);
+ break;
+
+ case 'r':
+ trp = va_arg(ap, Tokenrow *);
+ for (tp=trp->tp; tp<trp->lp&&tp->type!=NL; tp++) {
+ if (tp>trp->tp && tp->wslen)
+ fputc(' ', stderr);
+ fprintf(stderr, "%.*s", tp->len, tp->t);
+ }
+ break;
+
+ default:
+ fputc(*ep, stderr);
+ break;
+ }
+ } else
+ fputc(*ep, stderr);
+ }
+ va_end(ap);
+ fputc('\n', stderr);
+ if (type==FATAL)
+ exit(1);
+ if (type!=WARNING)
+ nerrs = 1;
+ fflush(stderr);
+}
diff --git a/src/tools/lcc/cpp/cpp.h b/src/tools/lcc/cpp/cpp.h
new file mode 100644
index 0000000..ddd7502
--- /dev/null
+++ b/src/tools/lcc/cpp/cpp.h
@@ -0,0 +1,166 @@
+#define INS 32768 /* input buffer */
+#define OBS 4096 /* outbut buffer */
+#define NARG 32 /* Max number arguments to a macro */
+#define NINCLUDE 32 /* Max number of include directories (-I) */
+#define NIF 32 /* depth of nesting of #if */
+#ifndef EOF
+#define EOF (-1)
+#endif
+#ifndef NULL
+#define NULL 0
+#endif
+
+#ifndef __alpha
+typedef unsigned char uchar;
+#endif
+
+enum toktype { END, UNCLASS, NAME, NUMBER, STRING, CCON, NL, WS, DSHARP,
+ EQ, NEQ, LEQ, GEQ, LSH, RSH, LAND, LOR, PPLUS, MMINUS,
+ ARROW, SBRA, SKET, LP, RP, DOT, AND, STAR, PLUS, MINUS,
+ TILDE, NOT, SLASH, PCT, LT, GT, CIRC, OR, QUEST,
+ COLON, ASGN, COMMA, SHARP, SEMIC, CBRA, CKET,
+ ASPLUS, ASMINUS, ASSTAR, ASSLASH, ASPCT, ASCIRC, ASLSH,
+ ASRSH, ASOR, ASAND, ELLIPS,
+ DSHARP1, NAME1, DEFINED, UMINUS };
+
+enum kwtype { KIF, KIFDEF, KIFNDEF, KELIF, KELSE, KENDIF, KINCLUDE, KDEFINE,
+ KUNDEF, KLINE, KWARNING, KERROR, KPRAGMA, KDEFINED,
+ KLINENO, KFILE, KDATE, KTIME, KSTDC, KEVAL };
+
+#define ISDEFINED 01 /* has #defined value */
+#define ISKW 02 /* is PP keyword */
+#define ISUNCHANGE 04 /* can't be #defined in PP */
+#define ISMAC 010 /* builtin macro, e.g. __LINE__ */
+
+#define EOB 0xFE /* sentinel for end of input buffer */
+#define EOFC 0xFD /* sentinel for end of input file */
+#define XPWS 1 /* token flag: white space to assure token sep. */
+
+typedef struct token {
+ unsigned char type;
+ unsigned char flag;
+ unsigned short hideset;
+ unsigned int wslen;
+ unsigned int len;
+ uchar *t;
+} Token;
+
+typedef struct tokenrow {
+ Token *tp; /* current one to scan */
+ Token *bp; /* base (allocated value) */
+ Token *lp; /* last+1 token used */
+ int max; /* number allocated */
+} Tokenrow;
+
+typedef struct source {
+ char *filename; /* name of file of the source */
+ int line; /* current line number */
+ int lineinc; /* adjustment for \\n lines */
+ uchar *inb; /* input buffer */
+ uchar *inp; /* input pointer */
+ uchar *inl; /* end of input */
+ int fd; /* input source */
+ int ifdepth; /* conditional nesting in include */
+ struct source *next; /* stack for #include */
+} Source;
+
+typedef struct nlist {
+ struct nlist *next;
+ uchar *name;
+ int len;
+ Tokenrow *vp; /* value as macro */
+ Tokenrow *ap; /* list of argument names, if any */
+ char val; /* value as preprocessor name */
+ char flag; /* is defined, is pp name */
+} Nlist;
+
+typedef struct includelist {
+ char deleted;
+ char always;
+ char *file;
+} Includelist;
+
+#define new(t) (t *)domalloc(sizeof(t))
+#define quicklook(a,b) (namebit[(a)&077] & (1<<((b)&037)))
+#define quickset(a,b) namebit[(a)&077] |= (1<<((b)&037))
+extern unsigned long namebit[077+1];
+
+enum errtype { WARNING, ERROR, FATAL };
+
+void expandlex(void);
+void fixlex(void);
+void setup(int, char **);
+int gettokens(Tokenrow *, int);
+int comparetokens(Tokenrow *, Tokenrow *);
+Source *setsource(char *, int, char *);
+void unsetsource(void);
+void puttokens(Tokenrow *);
+void process(Tokenrow *);
+void *domalloc(int);
+void dofree(void *);
+void error(enum errtype, char *, ...);
+void flushout(void);
+int fillbuf(Source *);
+int trigraph(Source *);
+int foldline(Source *);
+Nlist *lookup(Token *, int);
+void control(Tokenrow *);
+void dodefine(Tokenrow *);
+void doadefine(Tokenrow *, int);
+void doinclude(Tokenrow *);
+void appendDirToIncludeList( char *dir );
+void doif(Tokenrow *, enum kwtype);
+void expand(Tokenrow *, Nlist *);
+void builtin(Tokenrow *, int);
+int gatherargs(Tokenrow *, Tokenrow **, int *);
+void substargs(Nlist *, Tokenrow *, Tokenrow **);
+void expandrow(Tokenrow *, char *);
+void maketokenrow(int, Tokenrow *);
+Tokenrow *copytokenrow(Tokenrow *, Tokenrow *);
+Token *growtokenrow(Tokenrow *);
+Tokenrow *normtokenrow(Tokenrow *);
+void adjustrow(Tokenrow *, int);
+void movetokenrow(Tokenrow *, Tokenrow *);
+void insertrow(Tokenrow *, int, Tokenrow *);
+void peektokens(Tokenrow *, char *);
+void doconcat(Tokenrow *);
+Tokenrow *stringify(Tokenrow *);
+int lookuparg(Nlist *, Token *);
+long eval(Tokenrow *, int);
+void genline(void);
+void setempty(Tokenrow *);
+void makespace(Tokenrow *);
+char *outnum(char *, int);
+int digit(int);
+uchar *newstring(uchar *, int, int);
+int checkhideset(int, Nlist *);
+void prhideset(int);
+int newhideset(int, Nlist *);
+int unionhideset(int, int);
+void iniths(void);
+void setobjname(char *);
+#define rowlen(tokrow) ((tokrow)->lp - (tokrow)->bp)
+
+char *basepath( char *fname );
+
+extern char *outbufp;
+extern Token nltoken;
+extern Source *cursource;
+extern char *curtime;
+extern int incdepth;
+extern int ifdepth;
+extern int ifsatisfied[NIF];
+extern int Mflag;
+extern int skipping;
+extern int verbose;
+extern int Cplusplus;
+extern Nlist *kwdefined;
+extern Includelist includelist[NINCLUDE];
+extern char wd[];
+
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#include <io.h>
+#endif
+#include <fcntl.h>
diff --git a/src/tools/lcc/cpp/eval.c b/src/tools/lcc/cpp/eval.c
new file mode 100644
index 0000000..95a9e11
--- /dev/null
+++ b/src/tools/lcc/cpp/eval.c
@@ -0,0 +1,524 @@
+#include <stdlib.h>
+#include <string.h>
+#include "cpp.h"
+
+#define NSTAK 32
+#define SGN 0
+#define UNS 1
+#define UND 2
+
+#define UNSMARK 0x1000
+
+struct value {
+ long val;
+ int type;
+};
+
+/* conversion types */
+#define RELAT 1
+#define ARITH 2
+#define LOGIC 3
+#define SPCL 4
+#define SHIFT 5
+#define UNARY 6
+
+/* operator priority, arity, and conversion type, indexed by tokentype */
+struct pri {
+ char pri;
+ char arity;
+ char ctype;
+} priority[] = {
+ { 0, 0, 0 }, /* END */
+ { 0, 0, 0 }, /* UNCLASS */
+ { 0, 0, 0 }, /* NAME */
+ { 0, 0, 0 }, /* NUMBER */
+ { 0, 0, 0 }, /* STRING */
+ { 0, 0, 0 }, /* CCON */
+ { 0, 0, 0 }, /* NL */
+ { 0, 0, 0 }, /* WS */
+ { 0, 0, 0 }, /* DSHARP */
+ { 11, 2, RELAT }, /* EQ */
+ { 11, 2, RELAT }, /* NEQ */
+ { 12, 2, RELAT }, /* LEQ */
+ { 12, 2, RELAT }, /* GEQ */
+ { 13, 2, SHIFT }, /* LSH */
+ { 13, 2, SHIFT }, /* RSH */
+ { 7, 2, LOGIC }, /* LAND */
+ { 6, 2, LOGIC }, /* LOR */
+ { 0, 0, 0 }, /* PPLUS */
+ { 0, 0, 0 }, /* MMINUS */
+ { 0, 0, 0 }, /* ARROW */
+ { 0, 0, 0 }, /* SBRA */
+ { 0, 0, 0 }, /* SKET */
+ { 3, 0, 0 }, /* LP */
+ { 3, 0, 0 }, /* RP */
+ { 0, 0, 0 }, /* DOT */
+ { 10, 2, ARITH }, /* AND */
+ { 15, 2, ARITH }, /* STAR */
+ { 14, 2, ARITH }, /* PLUS */
+ { 14, 2, ARITH }, /* MINUS */
+ { 16, 1, UNARY }, /* TILDE */
+ { 16, 1, UNARY }, /* NOT */
+ { 15, 2, ARITH }, /* SLASH */
+ { 15, 2, ARITH }, /* PCT */
+ { 12, 2, RELAT }, /* LT */
+ { 12, 2, RELAT }, /* GT */
+ { 9, 2, ARITH }, /* CIRC */
+ { 8, 2, ARITH }, /* OR */
+ { 5, 2, SPCL }, /* QUEST */
+ { 5, 2, SPCL }, /* COLON */
+ { 0, 0, 0 }, /* ASGN */
+ { 4, 2, 0 }, /* COMMA */
+ { 0, 0, 0 }, /* SHARP */
+ { 0, 0, 0 }, /* SEMIC */
+ { 0, 0, 0 }, /* CBRA */
+ { 0, 0, 0 }, /* CKET */
+ { 0, 0, 0 }, /* ASPLUS */
+ { 0, 0, 0 }, /* ASMINUS */
+ { 0, 0, 0 }, /* ASSTAR */
+ { 0, 0, 0 }, /* ASSLASH */
+ { 0, 0, 0 }, /* ASPCT */
+ { 0, 0, 0 }, /* ASCIRC */
+ { 0, 0, 0 }, /* ASLSH */
+ { 0, 0, 0 }, /* ASRSH */
+ { 0, 0, 0 }, /* ASOR */
+ { 0, 0, 0 }, /* ASAND */
+ { 0, 0, 0 }, /* ELLIPS */
+ { 0, 0, 0 }, /* DSHARP1 */
+ { 0, 0, 0 }, /* NAME1 */
+ { 16, 1, UNARY }, /* DEFINED */
+ { 16, 0, UNARY }, /* UMINUS */
+};
+
+int evalop(struct pri);
+struct value tokval(Token *);
+struct value vals[NSTAK], *vp;
+enum toktype ops[NSTAK], *op;
+
+/*
+ * Evaluate an #if #elif #ifdef #ifndef line. trp->tp points to the keyword.
+ */
+long
+eval(Tokenrow *trp, int kw)
+{
+ Token *tp;
+ Nlist *np;
+ int ntok, rand;
+
+ trp->tp++;
+ if (kw==KIFDEF || kw==KIFNDEF) {
+ if (trp->lp - trp->bp != 4 || trp->tp->type!=NAME) {
+ error(ERROR, "Syntax error in #ifdef/#ifndef");
+ return 0;
+ }
+ np = lookup(trp->tp, 0);
+ return (kw==KIFDEF) == (np && np->flag&(ISDEFINED|ISMAC));
+ }
+ ntok = trp->tp - trp->bp;
+ kwdefined->val = KDEFINED; /* activate special meaning of defined */
+ expandrow(trp, "<if>");
+ kwdefined->val = NAME;
+ vp = vals;
+ op = ops;
+ *op++ = END;
+ for (rand=0, tp = trp->bp+ntok; tp < trp->lp; tp++) {
+ switch(tp->type) {
+ case WS:
+ case NL:
+ continue;
+
+ /* nilary */
+ case NAME:
+ case NAME1:
+ case NUMBER:
+ case CCON:
+ case STRING:
+ if (rand)
+ goto syntax;
+ *vp++ = tokval(tp);
+ rand = 1;
+ continue;
+
+ /* unary */
+ case DEFINED:
+ case TILDE:
+ case NOT:
+ if (rand)
+ goto syntax;
+ *op++ = tp->type;
+ continue;
+
+ /* unary-binary */
+ case PLUS: case MINUS: case STAR: case AND:
+ if (rand==0) {
+ if (tp->type==MINUS)
+ *op++ = UMINUS;
+ if (tp->type==STAR || tp->type==AND) {
+ error(ERROR, "Illegal operator * or & in #if/#elsif");
+ return 0;
+ }
+ continue;
+ }
+ /* flow through */
+
+ /* plain binary */
+ case EQ: case NEQ: case LEQ: case GEQ: case LSH: case RSH:
+ case LAND: case LOR: case SLASH: case PCT:
+ case LT: case GT: case CIRC: case OR: case QUEST:
+ case COLON: case COMMA:
+ if (rand==0)
+ goto syntax;
+ if (evalop(priority[tp->type])!=0)
+ return 0;
+ *op++ = tp->type;
+ rand = 0;
+ continue;
+
+ case LP:
+ if (rand)
+ goto syntax;
+ *op++ = LP;
+ continue;
+
+ case RP:
+ if (!rand)
+ goto syntax;
+ if (evalop(priority[RP])!=0)
+ return 0;
+ if (op<=ops || op[-1]!=LP) {
+ goto syntax;
+ }
+ op--;
+ continue;
+
+ default:
+ error(ERROR,"Bad operator (%t) in #if/#elsif", tp);
+ return 0;
+ }
+ }
+ if (rand==0)
+ goto syntax;
+ if (evalop(priority[END])!=0)
+ return 0;
+ if (op!=&ops[1] || vp!=&vals[1]) {
+ error(ERROR, "Botch in #if/#elsif");
+ return 0;
+ }
+ if (vals[0].type==UND)
+ error(ERROR, "Undefined expression value");
+ return vals[0].val;
+syntax:
+ error(ERROR, "Syntax error in #if/#elsif");
+ return 0;
+}
+
+int
+evalop(struct pri pri)
+{
+ struct value v1, v2;
+ long rv1, rv2;
+ int rtype, oper;
+
+ /* prevent compiler whining. */
+ v1.val = v2.val = 0;
+ v1.type = v2.type = 0;
+
+ rv2=0;
+ rtype=0;
+ while (pri.pri < priority[op[-1]].pri) {
+ oper = *--op;
+ if (priority[oper].arity==2) {
+ v2 = *--vp;
+ rv2 = v2.val;
+ }
+ v1 = *--vp;
+ rv1 = v1.val;
+/*lint -e574 -e644 */
+ switch (priority[oper].ctype) {
+ case 0:
+ default:
+ error(WARNING, "Syntax error in #if/#endif");
+ return 1;
+ case ARITH:
+ case RELAT:
+ if (v1.type==UNS || v2.type==UNS)
+ rtype = UNS;
+ else
+ rtype = SGN;
+ if (v1.type==UND || v2.type==UND)
+ rtype = UND;
+ if (priority[oper].ctype==RELAT && rtype==UNS) {
+ oper |= UNSMARK;
+ rtype = SGN;
+ }
+ break;
+ case SHIFT:
+ if (v1.type==UND || v2.type==UND)
+ rtype = UND;
+ else
+ rtype = v1.type;
+ if (rtype==UNS)
+ oper |= UNSMARK;
+ break;
+ case UNARY:
+ rtype = v1.type;
+ break;
+ case LOGIC:
+ case SPCL:
+ break;
+ }
+ switch (oper) {
+ case EQ: case EQ|UNSMARK:
+ rv1 = rv1==rv2; break;
+ case NEQ: case NEQ|UNSMARK:
+ rv1 = rv1!=rv2; break;
+ case LEQ:
+ rv1 = rv1<=rv2; break;
+ case GEQ:
+ rv1 = rv1>=rv2; break;
+ case LT:
+ rv1 = rv1<rv2; break;
+ case GT:
+ rv1 = rv1>rv2; break;
+ case LEQ|UNSMARK:
+ rv1 = (unsigned long)rv1<=rv2; break;
+ case GEQ|UNSMARK:
+ rv1 = (unsigned long)rv1>=rv2; break;
+ case LT|UNSMARK:
+ rv1 = (unsigned long)rv1<rv2; break;
+ case GT|UNSMARK:
+ rv1 = (unsigned long)rv1>rv2; break;
+ case LSH:
+ rv1 <<= rv2; break;
+ case LSH|UNSMARK:
+ rv1 = (unsigned long)rv1<<rv2; break;
+ case RSH:
+ rv1 >>= rv2; break;
+ case RSH|UNSMARK:
+ rv1 = (unsigned long)rv1>>rv2; break;
+ case LAND:
+ rtype = UND;
+ if (v1.type==UND)
+ break;
+ if (rv1!=0) {
+ if (v2.type==UND)
+ break;
+ rv1 = rv2!=0;
+ } else
+ rv1 = 0;
+ rtype = SGN;
+ break;
+ case LOR:
+ rtype = UND;
+ if (v1.type==UND)
+ break;
+ if (rv1==0) {
+ if (v2.type==UND)
+ break;
+ rv1 = rv2!=0;
+ } else
+ rv1 = 1;
+ rtype = SGN;
+ break;
+ case AND:
+ rv1 &= rv2; break;
+ case STAR:
+ rv1 *= rv2; break;
+ case PLUS:
+ rv1 += rv2; break;
+ case MINUS:
+ rv1 -= rv2; break;
+ case UMINUS:
+ if (v1.type==UND)
+ rtype = UND;
+ rv1 = -rv1; break;
+ case OR:
+ rv1 |= rv2; break;
+ case CIRC:
+ rv1 ^= rv2; break;
+ case TILDE:
+ rv1 = ~rv1; break;
+ case NOT:
+ rv1 = !rv1; if (rtype!=UND) rtype = SGN; break;
+ case SLASH:
+ if (rv2==0) {
+ rtype = UND;
+ break;
+ }
+ if (rtype==UNS)
+ rv1 /= (unsigned long)rv2;
+ else
+ rv1 /= rv2;
+ break;
+ case PCT:
+ if (rv2==0) {
+ rtype = UND;
+ break;
+ }
+ if (rtype==UNS)
+ rv1 %= (unsigned long)rv2;
+ else
+ rv1 %= rv2;
+ break;
+ case COLON:
+ if (op[-1] != QUEST)
+ error(ERROR, "Bad ?: in #if/endif");
+ else {
+ op--;
+ if ((--vp)->val==0)
+ v1 = v2;
+ rtype = v1.type;
+ rv1 = v1.val;
+ }
+ break;
+ case DEFINED:
+ break;
+ default:
+ error(ERROR, "Eval botch (unknown operator)");
+ return 1;
+ }
+/*lint +e574 +e644 */
+ v1.val = rv1;
+ v1.type = rtype;
+ *vp++ = v1;
+ }
+ return 0;
+}
+
+struct value
+tokval(Token *tp)
+{
+ struct value v;
+ Nlist *np;
+ int i, base, c;
+ unsigned long n;
+ uchar *p;
+
+ v.type = SGN;
+ v.val = 0;
+ switch (tp->type) {
+
+ case NAME:
+ v.val = 0;
+ break;
+
+ case NAME1:
+ if ((np = lookup(tp, 0)) != NULL && np->flag&(ISDEFINED|ISMAC))
+ v.val = 1;
+ break;
+
+ case NUMBER:
+ n = 0;
+ base = 10;
+ p = tp->t;
+ c = p[tp->len];
+ p[tp->len] = '\0';
+ if (*p=='0') {
+ base = 8;
+ if (p[1]=='x' || p[1]=='X') {
+ base = 16;
+ p++;
+ }
+ p++;
+ }
+ for (;; p++) {
+ if ((i = digit(*p)) < 0)
+ break;
+ if (i>=base)
+ error(WARNING,
+ "Bad digit in number %t", tp);
+ n *= base;
+ n += i;
+ }
+ if (n>=0x80000000 && base!=10)
+ v.type = UNS;
+ for (; *p; p++) {
+ if (*p=='u' || *p=='U')
+ v.type = UNS;
+ else if (*p=='l' || *p=='L')
+ ;
+ else {
+ error(ERROR,
+ "Bad number %t in #if/#elsif", tp);
+ break;
+ }
+ }
+ v.val = n;
+ tp->t[tp->len] = c;
+ break;
+
+ case CCON:
+ n = 0;
+ p = tp->t;
+ if (*p=='L') {
+ p += 1;
+ error(WARNING, "Wide char constant value undefined");
+ }
+ p += 1;
+ if (*p=='\\') {
+ p += 1;
+ if ((i = digit(*p))>=0 && i<=7) {
+ n = i;
+ p += 1;
+ if ((i = digit(*p))>=0 && i<=7) {
+ p += 1;
+ n <<= 3;
+ n += i;
+ if ((i = digit(*p))>=0 && i<=7) {
+ p += 1;
+ n <<= 3;
+ n += i;
+ }
+ }
+ } else if (*p=='x') {
+ p += 1;
+ while ((i = digit(*p))>=0 && i<=15) {
+ p += 1;
+ n <<= 4;
+ n += i;
+ }
+ } else {
+ static char cvcon[]
+ = "b\bf\fn\nr\rt\tv\v''\"\"??\\\\";
+ for (i=0; i<sizeof(cvcon); i+=2) {
+ if (*p == cvcon[i]) {
+ n = cvcon[i+1];
+ break;
+ }
+ }
+ p += 1;
+ if (i>=sizeof(cvcon))
+ error(WARNING,
+ "Undefined escape in character constant");
+ }
+ } else if (*p=='\'')
+ error(ERROR, "Empty character constant");
+ else
+ n = *p++;
+ if (*p!='\'')
+ error(WARNING, "Multibyte character constant undefined");
+ else if (n>127)
+ error(WARNING, "Character constant taken as not signed");
+ v.val = n;
+ break;
+
+ case STRING:
+ error(ERROR, "String in #if/#elsif");
+ break;
+ }
+ return v;
+}
+
+int
+digit(int i)
+{
+ if ('0'<=i && i<='9')
+ i -= '0';
+ else if ('a'<=i && i<='f')
+ i -= 'a'-10;
+ else if ('A'<=i && i<='F')
+ i -= 'A'-10;
+ else
+ i = -1;
+ return i;
+}
diff --git a/src/tools/lcc/cpp/getopt.c b/src/tools/lcc/cpp/getopt.c
new file mode 100644
index 0000000..c4d1af7
--- /dev/null
+++ b/src/tools/lcc/cpp/getopt.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <string.h>
+#define EPR fprintf(stderr,
+#define ERR(str, chr) if(opterr){EPR "%s%c\n", str, chr);}
+int opterr = 1;
+int optind = 1;
+int optopt;
+char *optarg;
+
+int
+lcc_getopt (int argc, char *const argv[], const char *opts)
+{
+ static int sp = 1;
+ int c;
+ char *cp;
+
+ if (sp == 1) {
+ if (optind >= argc ||
+ argv[optind][0] != '-' || argv[optind][1] == '\0')
+ return -1;
+ else if (strcmp(argv[optind], "--") == 0) {
+ optind++;
+ return -1;
+ }
+ }
+ optopt = c = argv[optind][sp];
+ if (c == ':' || (cp=strchr(opts, c)) == 0) {
+ ERR (": illegal option -- ", c);
+ if (argv[optind][++sp] == '\0') {
+ optind++;
+ sp = 1;
+ }
+ return '?';
+ }
+ if (*++cp == ':') {
+ if (argv[optind][sp+1] != '\0')
+ optarg = &argv[optind++][sp+1];
+ else if (++optind >= argc) {
+ ERR (": option requires an argument -- ", c);
+ sp = 1;
+ return '?';
+ } else
+ optarg = argv[optind++];
+ sp = 1;
+ } else {
+ if (argv[optind][++sp] == '\0') {
+ sp = 1;
+ optind++;
+ }
+ optarg = 0;
+ }
+ return c;
+}
diff --git a/src/tools/lcc/cpp/hideset.c b/src/tools/lcc/cpp/hideset.c
new file mode 100644
index 0000000..bd2540d
--- /dev/null
+++ b/src/tools/lcc/cpp/hideset.c
@@ -0,0 +1,112 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cpp.h"
+
+/*
+ * A hideset is a null-terminated array of Nlist pointers.
+ * They are referred to by indices in the hidesets array.
+ * Hideset 0 is empty.
+ */
+
+#define HSSIZ 32
+typedef Nlist **Hideset;
+Hideset *hidesets;
+int nhidesets = 0;
+int maxhidesets = 3;
+int inserths(Hideset, Hideset, Nlist *);
+
+/*
+ * Test for membership in a hideset
+ */
+int
+checkhideset(int hs, Nlist *np)
+{
+ Hideset hsp;
+
+ if (hs>=nhidesets)
+ abort();
+ for (hsp = hidesets[hs]; *hsp; hsp++) {
+ if (*hsp == np)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Return the (possibly new) hideset obtained by adding np to hs.
+ */
+int
+newhideset(int hs, Nlist *np)
+{
+ int i, len;
+ Nlist *nhs[HSSIZ+3];
+ Hideset hs1, hs2;
+
+ len = inserths(nhs, hidesets[hs], np);
+ for (i=0; i<nhidesets; i++) {
+ for (hs1=nhs, hs2=hidesets[i]; *hs1==*hs2; hs1++, hs2++)
+ if (*hs1 == NULL)
+ return i;
+ }
+ if (len>=HSSIZ)
+ return hs;
+ if (nhidesets >= maxhidesets) {
+ maxhidesets = 3*maxhidesets/2+1;
+ hidesets = (Hideset *)realloc(hidesets, (sizeof (Hideset *))*maxhidesets);
+ if (hidesets == NULL)
+ error(FATAL, "Out of memory from realloc");
+ }
+ hs1 = (Hideset)domalloc(len*sizeof(Hideset));
+ memmove(hs1, nhs, len*sizeof(Hideset));
+ hidesets[nhidesets] = hs1;
+ return nhidesets++;
+}
+
+int
+inserths(Hideset dhs, Hideset shs, Nlist *np)
+{
+ Hideset odhs = dhs;
+
+ while (*shs && *shs < np)
+ *dhs++ = *shs++;
+ if (*shs != np)
+ *dhs++ = np;
+ do {
+ *dhs++ = *shs;
+ } while (*shs++);
+ return dhs - odhs;
+}
+
+/*
+ * Hideset union
+ */
+int
+unionhideset(int hs1, int hs2)
+{
+ Hideset hp;
+
+ for (hp = hidesets[hs2]; *hp; hp++)
+ hs1 = newhideset(hs1, *hp);
+ return hs1;
+}
+
+void
+iniths(void)
+{
+ hidesets = (Hideset *)domalloc(maxhidesets*sizeof(Hideset *));
+ hidesets[0] = (Hideset)domalloc(sizeof(Hideset));
+ *hidesets[0] = NULL;
+ nhidesets++;
+}
+
+void
+prhideset(int hs)
+{
+ Hideset np;
+
+ for (np = hidesets[hs]; *np; np++) {
+ fprintf(stderr, (char*)(*np)->name, (*np)->len);
+ fprintf(stderr, " ");
+ }
+}
diff --git a/src/tools/lcc/cpp/include.c b/src/tools/lcc/cpp/include.c
new file mode 100644
index 0000000..5ecd8b3
--- /dev/null
+++ b/src/tools/lcc/cpp/include.c
@@ -0,0 +1,153 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cpp.h"
+
+Includelist includelist[NINCLUDE];
+
+extern char *objname;
+
+void appendDirToIncludeList( char *dir )
+{
+ int i;
+ char *fqdir;
+
+ fqdir = (char *)newstring( (uchar *)includelist[NINCLUDE-1].file, 256, 0 );
+ strcat( fqdir, "/" );
+ strcat( fqdir, dir );
+
+ //avoid adding it more than once
+ for (i=NINCLUDE-2; i>=0; i--) {
+ if (includelist[i].file &&
+ !strcmp (includelist[i].file, fqdir)) {
+ return;
+ }
+ }
+
+ for (i=NINCLUDE-2; i>=0; i--) {
+ if (includelist[i].file==NULL) {
+ includelist[i].always = 1;
+ includelist[i].file = fqdir;
+ break;
+ }
+ }
+ if (i<0)
+ error(FATAL, "Too many -I directives");
+}
+
+void
+doinclude(Tokenrow *trp)
+{
+ char fname[256], iname[256];
+ Includelist *ip;
+ int angled, len, fd, i;
+
+ trp->tp += 1;
+ if (trp->tp>=trp->lp)
+ goto syntax;
+ if (trp->tp->type!=STRING && trp->tp->type!=LT) {
+ len = trp->tp - trp->bp;
+ expandrow(trp, "<include>");
+ trp->tp = trp->bp+len;
+ }
+ if (trp->tp->type==STRING) {
+ len = trp->tp->len-2;
+ if (len > sizeof(fname) - 1)
+ len = sizeof(fname) - 1;
+ strncpy(fname, (char*)trp->tp->t+1, len);
+ angled = 0;
+ } else if (trp->tp->type==LT) {
+ len = 0;
+ trp->tp++;
+ while (trp->tp->type!=GT) {
+ if (trp->tp>trp->lp || len+trp->tp->len+2 >= sizeof(fname))
+ goto syntax;
+ strncpy(fname+len, (char*)trp->tp->t, trp->tp->len);
+ len += trp->tp->len;
+ trp->tp++;
+ }
+ angled = 1;
+ } else
+ goto syntax;
+ trp->tp += 2;
+ if (trp->tp < trp->lp || len==0)
+ goto syntax;
+ fname[len] = '\0';
+
+ appendDirToIncludeList( basepath( fname ) );
+
+ if (fname[0]=='/') {
+ fd = open(fname, 0);
+ strcpy(iname, fname);
+ } else for (fd = -1,i=NINCLUDE-1; i>=0; i--) {
+ ip = &includelist[i];
+ if (ip->file==NULL || ip->deleted || (angled && ip->always==0))
+ continue;
+ if (strlen(fname)+strlen(ip->file)+2 > sizeof(iname))
+ continue;
+ strcpy(iname, ip->file);
+ strcat(iname, "/");
+ strcat(iname, fname);
+ if ((fd = open(iname, 0)) >= 0)
+ break;
+ }
+ if ( Mflag>1 || (!angled&&Mflag==1) ) {
+ write(1,objname,strlen(objname));
+ write(1,iname,strlen(iname));
+ write(1,"\n",1);
+ }
+ if (fd >= 0) {
+ if (++incdepth > 10)
+ error(FATAL, "#include too deeply nested");
+ setsource((char*)newstring((uchar*)iname, strlen(iname), 0), fd, NULL);
+ genline();
+ } else {
+ trp->tp = trp->bp+2;
+ error(ERROR, "Could not find include file %r", trp);
+ }
+ return;
+syntax:
+ error(ERROR, "Syntax error in #include");
+}
+
+/*
+ * Generate a line directive for cursource
+ */
+void
+genline(void)
+{
+ static Token ta = { UNCLASS };
+ static Tokenrow tr = { &ta, &ta, &ta+1, 1 };
+ uchar *p;
+
+ ta.t = p = (uchar*)outbufp;
+ strcpy((char*)p, "#line ");
+ p += sizeof("#line ")-1;
+ p = (uchar*)outnum((char*)p, cursource->line);
+ *p++ = ' '; *p++ = '"';
+ if (cursource->filename[0]!='/' && wd[0]) {
+ strcpy((char*)p, wd);
+ p += strlen(wd);
+ *p++ = '/';
+ }
+ strcpy((char*)p, cursource->filename);
+ p += strlen((char*)p);
+ *p++ = '"'; *p++ = '\n';
+ ta.len = (char*)p-outbufp;
+ outbufp = (char*)p;
+ tr.tp = tr.bp;
+ puttokens(&tr);
+}
+
+void
+setobjname(char *f)
+{
+ int n = strlen(f);
+ objname = (char*)domalloc(n+5);
+ strcpy(objname,f);
+ if(objname[n-2]=='.'){
+ strcpy(objname+n-1,"$O: ");
+ }else{
+ strcpy(objname+n,"$O: ");
+ }
+}
diff --git a/src/tools/lcc/cpp/lex.c b/src/tools/lcc/cpp/lex.c
new file mode 100644
index 0000000..8030354
--- /dev/null
+++ b/src/tools/lcc/cpp/lex.c
@@ -0,0 +1,580 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cpp.h"
+
+/*
+ * lexical FSM encoding
+ * when in state state, and one of the characters
+ * in ch arrives, enter nextstate.
+ * States >= S_SELF are either final, or at least require special action.
+ * In 'fsm' there is a line for each state X charset X nextstate.
+ * List chars that overwrite previous entries later (e.g. C_ALPH
+ * can be overridden by '_' by a later entry; and C_XX is the
+ * the universal set, and should always be first.
+ * States above S_SELF are represented in the big table as negative values.
+ * S_SELF and S_SELFB encode the resulting token type in the upper bits.
+ * These actions differ in that S_SELF doesn't have a lookahead char,
+ * S_SELFB does.
+ *
+ * The encoding is blown out into a big table for time-efficiency.
+ * Entries have
+ * nextstate: 6 bits; ?\ marker: 1 bit; tokentype: 9 bits.
+ */
+
+#define MAXSTATE 32
+#define ACT(tok,act) ((tok<<7)+act)
+#define QBSBIT 0100
+#define GETACT(st) (st>>7)&0x1ff
+
+/* character classes */
+#define C_WS 1
+#define C_ALPH 2
+#define C_NUM 3
+#define C_EOF 4
+#define C_XX 5
+
+enum state {
+ START=0, NUM1, NUM2, NUM3, ID1, ST1, ST2, ST3, COM1, COM2, COM3, COM4,
+ CC1, CC2, WS1, PLUS1, MINUS1, STAR1, SLASH1, PCT1, SHARP1,
+ CIRC1, GT1, GT2, LT1, LT2, OR1, AND1, ASG1, NOT1, DOTS1,
+ S_SELF=MAXSTATE, S_SELFB, S_EOF, S_NL, S_EOFSTR,
+ S_STNL, S_COMNL, S_EOFCOM, S_COMMENT, S_EOB, S_WS, S_NAME
+};
+
+int tottok;
+int tokkind[256];
+struct fsm {
+ int state; /* if in this state */
+ uchar ch[4]; /* and see one of these characters */
+ int nextstate; /* enter this state if +ve */
+};
+
+/*const*/ struct fsm fsm[] = {
+ /* start state */
+ {START, { C_XX }, ACT(UNCLASS,S_SELF)},
+ {START, { ' ', '\t', '\v' }, WS1},
+ {START, { C_NUM }, NUM1},
+ {START, { '.' }, NUM3},
+ {START, { C_ALPH }, ID1},
+ {START, { 'L' }, ST1},
+ {START, { '"' }, ST2},
+ {START, { '\'' }, CC1},
+ {START, { '/' }, COM1},
+ {START, { EOFC }, S_EOF},
+ {START, { '\n' }, S_NL},
+ {START, { '-' }, MINUS1},
+ {START, { '+' }, PLUS1},
+ {START, { '<' }, LT1},
+ {START, { '>' }, GT1},
+ {START, { '=' }, ASG1},
+ {START, { '!' }, NOT1},
+ {START, { '&' }, AND1},
+ {START, { '|' }, OR1},
+ {START, { '#' }, SHARP1},
+ {START, { '%' }, PCT1},
+ {START, { '[' }, ACT(SBRA,S_SELF)},
+ {START, { ']' }, ACT(SKET,S_SELF)},
+ {START, { '(' }, ACT(LP,S_SELF)},
+ {START, { ')' }, ACT(RP,S_SELF)},
+ {START, { '*' }, STAR1},
+ {START, { ',' }, ACT(COMMA,S_SELF)},
+ {START, { '?' }, ACT(QUEST,S_SELF)},
+ {START, { ':' }, ACT(COLON,S_SELF)},
+ {START, { ';' }, ACT(SEMIC,S_SELF)},
+ {START, { '{' }, ACT(CBRA,S_SELF)},
+ {START, { '}' }, ACT(CKET,S_SELF)},
+ {START, { '~' }, ACT(TILDE,S_SELF)},
+ {START, { '^' }, CIRC1},
+
+ /* saw a digit */
+ {NUM1, { C_XX }, ACT(NUMBER,S_SELFB)},
+ {NUM1, { C_NUM, C_ALPH, '.' }, NUM1},
+ {NUM1, { 'E', 'e' }, NUM2},
+ {NUM1, { '_' }, ACT(NUMBER,S_SELFB)},
+
+ /* saw possible start of exponent, digits-e */
+ {NUM2, { C_XX }, ACT(NUMBER,S_SELFB)},
+ {NUM2, { '+', '-' }, NUM1},
+ {NUM2, { C_NUM, C_ALPH }, NUM1},
+ {NUM2, { '_' }, ACT(NUMBER,S_SELFB)},
+
+ /* saw a '.', which could be a number or an operator */
+ {NUM3, { C_XX }, ACT(DOT,S_SELFB)},
+ {NUM3, { '.' }, DOTS1},
+ {NUM3, { C_NUM }, NUM1},
+
+ {DOTS1, { C_XX }, ACT(UNCLASS, S_SELFB)},
+ {DOTS1, { C_NUM }, NUM1},
+ {DOTS1, { '.' }, ACT(ELLIPS, S_SELF)},
+
+ /* saw a letter or _ */
+ {ID1, { C_XX }, ACT(NAME,S_NAME)},
+ {ID1, { C_ALPH, C_NUM }, ID1},
+
+ /* saw L (start of wide string?) */
+ {ST1, { C_XX }, ACT(NAME,S_NAME)},
+ {ST1, { C_ALPH, C_NUM }, ID1},
+ {ST1, { '"' }, ST2},
+ {ST1, { '\'' }, CC1},
+
+ /* saw " beginning string */
+ {ST2, { C_XX }, ST2},
+ {ST2, { '"' }, ACT(STRING, S_SELF)},
+ {ST2, { '\\' }, ST3},
+ {ST2, { '\n' }, S_STNL},
+ {ST2, { EOFC }, S_EOFSTR},
+
+ /* saw \ in string */
+ {ST3, { C_XX }, ST2},
+ {ST3, { '\n' }, S_STNL},
+ {ST3, { EOFC }, S_EOFSTR},
+
+ /* saw ' beginning character const */
+ {CC1, { C_XX }, CC1},
+ {CC1, { '\'' }, ACT(CCON, S_SELF)},
+ {CC1, { '\\' }, CC2},
+ {CC1, { '\n' }, S_STNL},
+ {CC1, { EOFC }, S_EOFSTR},
+
+ /* saw \ in ccon */
+ {CC2, { C_XX }, CC1},
+ {CC2, { '\n' }, S_STNL},
+ {CC2, { EOFC }, S_EOFSTR},
+
+ /* saw /, perhaps start of comment */
+ {COM1, { C_XX }, ACT(SLASH, S_SELFB)},
+ {COM1, { '=' }, ACT(ASSLASH, S_SELF)},
+ {COM1, { '*' }, COM2},
+ {COM1, { '/' }, COM4},
+
+ /* saw / then *, start of comment */
+ {COM2, { C_XX }, COM2},
+ {COM2, { '\n' }, S_COMNL},
+ {COM2, { '*' }, COM3},
+ {COM2, { EOFC }, S_EOFCOM},
+
+ /* saw the * possibly ending a comment */
+ {COM3, { C_XX }, COM2},
+ {COM3, { '\n' }, S_COMNL},
+ {COM3, { '*' }, COM3},
+ {COM3, { '/' }, S_COMMENT},
+
+ /* // comment */
+ {COM4, { C_XX }, COM4},
+ {COM4, { '\n' }, S_NL},
+ {COM4, { EOFC }, S_EOFCOM},
+
+ /* saw white space, eat it up */
+ {WS1, { C_XX }, S_WS},
+ {WS1, { ' ', '\t', '\v' }, WS1},
+
+ /* saw -, check --, -=, -> */
+ {MINUS1, { C_XX }, ACT(MINUS, S_SELFB)},
+ {MINUS1, { '-' }, ACT(MMINUS, S_SELF)},
+ {MINUS1, { '=' }, ACT(ASMINUS,S_SELF)},
+ {MINUS1, { '>' }, ACT(ARROW,S_SELF)},
+
+ /* saw +, check ++, += */
+ {PLUS1, { C_XX }, ACT(PLUS, S_SELFB)},
+ {PLUS1, { '+' }, ACT(PPLUS, S_SELF)},
+ {PLUS1, { '=' }, ACT(ASPLUS, S_SELF)},
+
+ /* saw <, check <<, <<=, <= */
+ {LT1, { C_XX }, ACT(LT, S_SELFB)},
+ {LT1, { '<' }, LT2},
+ {LT1, { '=' }, ACT(LEQ, S_SELF)},
+ {LT2, { C_XX }, ACT(LSH, S_SELFB)},
+ {LT2, { '=' }, ACT(ASLSH, S_SELF)},
+
+ /* saw >, check >>, >>=, >= */
+ {GT1, { C_XX }, ACT(GT, S_SELFB)},
+ {GT1, { '>' }, GT2},
+ {GT1, { '=' }, ACT(GEQ, S_SELF)},
+ {GT2, { C_XX }, ACT(RSH, S_SELFB)},
+ {GT2, { '=' }, ACT(ASRSH, S_SELF)},
+
+ /* = */
+ {ASG1, { C_XX }, ACT(ASGN, S_SELFB)},
+ {ASG1, { '=' }, ACT(EQ, S_SELF)},
+
+ /* ! */
+ {NOT1, { C_XX }, ACT(NOT, S_SELFB)},
+ {NOT1, { '=' }, ACT(NEQ, S_SELF)},
+
+ /* & */
+ {AND1, { C_XX }, ACT(AND, S_SELFB)},
+ {AND1, { '&' }, ACT(LAND, S_SELF)},
+ {AND1, { '=' }, ACT(ASAND, S_SELF)},
+
+ /* | */
+ {OR1, { C_XX }, ACT(OR, S_SELFB)},
+ {OR1, { '|' }, ACT(LOR, S_SELF)},
+ {OR1, { '=' }, ACT(ASOR, S_SELF)},
+
+ /* # */
+ {SHARP1, { C_XX }, ACT(SHARP, S_SELFB)},
+ {SHARP1, { '#' }, ACT(DSHARP, S_SELF)},
+
+ /* % */
+ {PCT1, { C_XX }, ACT(PCT, S_SELFB)},
+ {PCT1, { '=' }, ACT(ASPCT, S_SELF)},
+
+ /* * */
+ {STAR1, { C_XX }, ACT(STAR, S_SELFB)},
+ {STAR1, { '=' }, ACT(ASSTAR, S_SELF)},
+
+ /* ^ */
+ {CIRC1, { C_XX }, ACT(CIRC, S_SELFB)},
+ {CIRC1, { '=' }, ACT(ASCIRC, S_SELF)},
+
+ {-1}
+};
+
+/* first index is char, second is state */
+/* increase #states to power of 2 to encourage use of shift */
+short bigfsm[256][MAXSTATE];
+
+void
+expandlex(void)
+{
+ /*const*/ struct fsm *fp;
+ int i, j, nstate;
+
+ for (fp = fsm; fp->state>=0; fp++) {
+ for (i=0; fp->ch[i]; i++) {
+ nstate = fp->nextstate;
+ if (nstate >= S_SELF)
+ nstate = ~nstate;
+ switch (fp->ch[i]) {
+
+ case C_XX: /* random characters */
+ for (j=0; j<256; j++)
+ bigfsm[j][fp->state] = nstate;
+ continue;
+ case C_ALPH:
+ for (j=0; j<=256; j++)
+ if (('a'<=j&&j<='z') || ('A'<=j&&j<='Z')
+ || j=='_')
+ bigfsm[j][fp->state] = nstate;
+ continue;
+ case C_NUM:
+ for (j='0'; j<='9'; j++)
+ bigfsm[j][fp->state] = nstate;
+ continue;
+ default:
+ bigfsm[fp->ch[i]][fp->state] = nstate;
+ }
+ }
+ }
+ /* install special cases for ? (trigraphs), \ (splicing), runes, and EOB */
+ for (i=0; i<MAXSTATE; i++) {
+ for (j=0; j<0xFF; j++)
+ if (j=='?' || j=='\\') {
+ if (bigfsm[j][i]>0)
+ bigfsm[j][i] = ~bigfsm[j][i];
+ bigfsm[j][i] &= ~QBSBIT;
+ }
+ bigfsm[EOB][i] = ~S_EOB;
+ if (bigfsm[EOFC][i]>=0)
+ bigfsm[EOFC][i] = ~S_EOF;
+ }
+}
+
+void
+fixlex(void)
+{
+ /* do C++ comments? */
+ if (Cplusplus==0)
+ bigfsm['/'][COM1] = bigfsm['x'][COM1];
+}
+
+/*
+ * fill in a row of tokens from input, terminated by NL or END
+ * First token is put at trp->lp.
+ * Reset is non-zero when the input buffer can be "rewound."
+ * The value is a flag indicating that possible macros have
+ * been seen in the row.
+ */
+int
+gettokens(Tokenrow *trp, int reset)
+{
+ register int c, state, oldstate;
+ register uchar *ip;
+ register Token *tp, *maxp;
+ int runelen;
+ Source *s = cursource;
+ int nmac = 0;
+
+ tp = trp->lp;
+ ip = s->inp;
+ if (reset) {
+ s->lineinc = 0;
+ if (ip>=s->inl) { /* nothing in buffer */
+ s->inl = s->inb;
+ fillbuf(s);
+ ip = s->inp = s->inb;
+ } else if (ip >= s->inb+(3*INS/4)) {
+ memmove(s->inb, ip, 4+s->inl-ip);
+ s->inl = s->inb+(s->inl-ip);
+ ip = s->inp = s->inb;
+ }
+ }
+ maxp = &trp->bp[trp->max];
+ runelen = 1;
+ for (;;) {
+ continue2:
+ if (tp>=maxp) {
+ trp->lp = tp;
+ tp = growtokenrow(trp);
+ maxp = &trp->bp[trp->max];
+ }
+ tp->type = UNCLASS;
+ tp->hideset = 0;
+ tp->t = ip;
+ tp->wslen = 0;
+ tp->flag = 0;
+ state = START;
+ for (;;) {
+ oldstate = state;
+ c = *ip;
+ if ((state = bigfsm[c][state]) >= 0) {
+ ip += runelen;
+ runelen = 1;
+ continue;
+ }
+ state = ~state;
+ reswitch:
+ switch (state&0177) {
+ case S_SELF:
+ ip += runelen;
+ runelen = 1;
+ case S_SELFB:
+ tp->type = GETACT(state);
+ tp->len = ip - tp->t;
+ tp++;
+ goto continue2;
+
+ case S_NAME: /* like S_SELFB but with nmac check */
+ tp->type = NAME;
+ tp->len = ip - tp->t;
+ nmac |= quicklook(tp->t[0], tp->len>1?tp->t[1]:0);
+ tp++;
+ goto continue2;
+
+ case S_WS:
+ tp->wslen = ip - tp->t;
+ tp->t = ip;
+ state = START;
+ continue;
+
+ default:
+ if ((state&QBSBIT)==0) {
+ ip += runelen;
+ runelen = 1;
+ continue;
+ }
+ state &= ~QBSBIT;
+ s->inp = ip;
+ if (c=='?') { /* check trigraph */
+ if (trigraph(s)) {
+ state = oldstate;
+ continue;
+ }
+ goto reswitch;
+ }
+ if (c=='\\') { /* line-folding */
+ if (foldline(s)) {
+ s->lineinc++;
+ state = oldstate;
+ continue;
+ }
+ goto reswitch;
+ }
+ error(WARNING, "Lexical botch in cpp");
+ ip += runelen;
+ runelen = 1;
+ continue;
+
+ case S_EOB:
+ s->inp = ip;
+ fillbuf(cursource);
+ state = oldstate;
+ continue;
+
+ case S_EOF:
+ tp->type = END;
+ tp->len = 0;
+ s->inp = ip;
+ if (tp!=trp->bp && (tp-1)->type!=NL && cursource->fd!=-1)
+ error(WARNING,"No newline at end of file");
+ trp->lp = tp+1;
+ return nmac;
+
+ case S_STNL:
+ error(ERROR, "Unterminated string or char const");
+ case S_NL:
+ tp->t = ip;
+ tp->type = NL;
+ tp->len = 1;
+ tp->wslen = 0;
+ s->lineinc++;
+ s->inp = ip+1;
+ trp->lp = tp+1;
+ return nmac;
+
+ case S_EOFSTR:
+ error(FATAL, "EOF in string or char constant");
+ break;
+
+ case S_COMNL:
+ s->lineinc++;
+ state = COM2;
+ ip += runelen;
+ runelen = 1;
+ if (ip >= s->inb+(7*INS/8)) { /* very long comment */
+ memmove(tp->t, ip, 4+s->inl-ip);
+ s->inl -= ip-tp->t;
+ ip = tp->t+1;
+ }
+ continue;
+
+ case S_EOFCOM:
+ error(WARNING, "EOF inside comment");
+ --ip;
+ case S_COMMENT:
+ ++ip;
+ tp->t = ip;
+ tp->t[-1] = ' ';
+ tp->wslen = 1;
+ state = START;
+ continue;
+ }
+ break;
+ }
+ ip += runelen;
+ runelen = 1;
+ tp->len = ip - tp->t;
+ tp++;
+ }
+}
+
+/* have seen ?; handle the trigraph it starts (if any) else 0 */
+int
+trigraph(Source *s)
+{
+ int c;
+
+ while (s->inp+2 >= s->inl && fillbuf(s)!=EOF)
+ ;
+ if (s->inp[1]!='?')
+ return 0;
+ c = 0;
+ switch(s->inp[2]) {
+ case '=':
+ c = '#'; break;
+ case '(':
+ c = '['; break;
+ case '/':
+ c = '\\'; break;
+ case ')':
+ c = ']'; break;
+ case '\'':
+ c = '^'; break;
+ case '<':
+ c = '{'; break;
+ case '!':
+ c = '|'; break;
+ case '>':
+ c = '}'; break;
+ case '-':
+ c = '~'; break;
+ }
+ if (c) {
+ *s->inp = c;
+ memmove(s->inp+1, s->inp+3, s->inl-s->inp+2);
+ s->inl -= 2;
+ }
+ return c;
+}
+
+int
+foldline(Source *s)
+{
+ while (s->inp+1 >= s->inl && fillbuf(s)!=EOF)
+ ;
+ if (s->inp[1] == '\n') {
+ memmove(s->inp, s->inp+2, s->inl-s->inp+3);
+ s->inl -= 2;
+ return 1;
+ }
+ return 0;
+}
+
+int
+fillbuf(Source *s)
+{
+ int n, nr;
+
+ nr = INS/8;
+ if ((char *)s->inl+nr > (char *)s->inb+INS)
+ error(FATAL, "Input buffer overflow");
+ if (s->fd<0 || (n=read(s->fd, (char *)s->inl, INS/8)) <= 0)
+ n = 0;
+ if ((*s->inp&0xff) == EOB) /* sentinel character appears in input */
+ *s->inp = EOFC;
+ s->inl += n;
+ s->inl[0] = s->inl[1]= s->inl[2]= s->inl[3] = EOB;
+ if (n==0) {
+ s->inl[0] = s->inl[1]= s->inl[2]= s->inl[3] = EOFC;
+ return EOF;
+ }
+ return 0;
+}
+
+/*
+ * Push down to new source of characters.
+ * If fd>0 and str==NULL, then from a file `name';
+ * if fd==-1 and str, then from the string.
+ */
+Source *
+setsource(char *name, int fd, char *str)
+{
+ Source *s = new(Source);
+ int len;
+
+ s->line = 1;
+ s->lineinc = 0;
+ s->fd = fd;
+ s->filename = name;
+ s->next = cursource;
+ s->ifdepth = 0;
+ cursource = s;
+ /* slop at right for EOB */
+ if (str) {
+ len = strlen(str);
+ s->inb = domalloc(len+4);
+ s->inp = s->inb;
+ strncpy((char *)s->inp, str, len);
+ } else {
+ s->inb = domalloc(INS+4);
+ s->inp = s->inb;
+ len = 0;
+ }
+ s->inl = s->inp+len;
+ s->inl[0] = s->inl[1] = EOB;
+ return s;
+}
+
+void
+unsetsource(void)
+{
+ Source *s = cursource;
+
+ if (s->fd>=0) {
+ close(s->fd);
+ dofree(s->inb);
+ }
+ cursource = s->next;
+ dofree(s);
+}
diff --git a/src/tools/lcc/cpp/macro.c b/src/tools/lcc/cpp/macro.c
new file mode 100644
index 0000000..2972083
--- /dev/null
+++ b/src/tools/lcc/cpp/macro.c
@@ -0,0 +1,514 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cpp.h"
+
+/*
+ * do a macro definition. tp points to the name being defined in the line
+ */
+void
+dodefine(Tokenrow *trp)
+{
+ Token *tp;
+ Nlist *np;
+ Tokenrow *def, *args;
+
+ tp = trp->tp+1;
+ if (tp>=trp->lp || tp->type!=NAME) {
+ error(ERROR, "#defined token is not a name");
+ return;
+ }
+ np = lookup(tp, 1);
+ if (np->flag&ISUNCHANGE) {
+ error(ERROR, "#defined token %t can't be redefined", tp);
+ return;
+ }
+ /* collect arguments */
+ tp += 1;
+ args = NULL;
+ if (tp<trp->lp && tp->type==LP && tp->wslen==0) {
+ /* macro with args */
+ int narg = 0;
+ tp += 1;
+ args = new(Tokenrow);
+ maketokenrow(2, args);
+ if (tp->type!=RP) {
+ int err = 0;
+ for (;;) {
+ Token *atp;
+ if (tp->type!=NAME) {
+ err++;
+ break;
+ }
+ if (narg>=args->max)
+ growtokenrow(args);
+ for (atp=args->bp; atp<args->lp; atp++)
+ if (atp->len==tp->len
+ && strncmp((char*)atp->t, (char*)tp->t, tp->len)==0)
+ error(ERROR, "Duplicate macro argument");
+ *args->lp++ = *tp;
+ narg++;
+ tp += 1;
+ if (tp->type==RP)
+ break;
+ if (tp->type!=COMMA) {
+ err++;
+ break;
+ }
+ tp += 1;
+ }
+ if (err) {
+ error(ERROR, "Syntax error in macro parameters");
+ return;
+ }
+ }
+ tp += 1;
+ }
+ trp->tp = tp;
+ if (((trp->lp)-1)->type==NL)
+ trp->lp -= 1;
+ def = normtokenrow(trp);
+ if (np->flag&ISDEFINED) {
+ if (comparetokens(def, np->vp)
+ || (np->ap==NULL) != (args==NULL)
+ || (np->ap && comparetokens(args, np->ap)))
+ error(ERROR, "Macro redefinition of %t", trp->bp+2);
+ }
+ if (args) {
+ Tokenrow *tap;
+ tap = normtokenrow(args);
+ dofree(args->bp);
+ args = tap;
+ }
+ np->ap = args;
+ np->vp = def;
+ np->flag |= ISDEFINED;
+}
+
+/*
+ * Definition received via -D or -U
+ */
+void
+doadefine(Tokenrow *trp, int type)
+{
+ Nlist *np;
+ static Token onetoken[1] = {{ NUMBER, 0, 0, 0, 1, (uchar*)"1" }};
+ static Tokenrow onetr = { onetoken, onetoken, onetoken+1, 1 };
+
+ trp->tp = trp->bp;
+ if (type=='U') {
+ if (trp->lp-trp->tp != 2 || trp->tp->type!=NAME)
+ goto syntax;
+ if ((np = lookup(trp->tp, 0)) == NULL)
+ return;
+ np->flag &= ~ISDEFINED;
+ return;
+ }
+ if (trp->tp >= trp->lp || trp->tp->type!=NAME)
+ goto syntax;
+ np = lookup(trp->tp, 1);
+ np->flag |= ISDEFINED;
+ trp->tp += 1;
+ if (trp->tp >= trp->lp || trp->tp->type==END) {
+ np->vp = &onetr;
+ return;
+ }
+ if (trp->tp->type!=ASGN)
+ goto syntax;
+ trp->tp += 1;
+ if ((trp->lp-1)->type == END)
+ trp->lp -= 1;
+ np->vp = normtokenrow(trp);
+ return;
+syntax:
+ error(FATAL, "Illegal -D or -U argument %r", trp);
+}
+
+/*
+ * Do macro expansion in a row of tokens.
+ * Flag is NULL if more input can be gathered.
+ */
+void
+expandrow(Tokenrow *trp, char *flag)
+{
+ Token *tp;
+ Nlist *np;
+
+ if (flag)
+ setsource(flag, -1, "");
+ for (tp = trp->tp; tp<trp->lp; ) {
+ if (tp->type!=NAME
+ || quicklook(tp->t[0], tp->len>1?tp->t[1]:0)==0
+ || (np = lookup(tp, 0))==NULL
+ || (np->flag&(ISDEFINED|ISMAC))==0
+ || (tp->hideset && checkhideset(tp->hideset, np))) {
+ tp++;
+ continue;
+ }
+ trp->tp = tp;
+ if (np->val==KDEFINED) {
+ tp->type = DEFINED;
+ if ((tp+1)<trp->lp && (tp+1)->type==NAME)
+ (tp+1)->type = NAME1;
+ else if ((tp+3)<trp->lp && (tp+1)->type==LP
+ && (tp+2)->type==NAME && (tp+3)->type==RP)
+ (tp+2)->type = NAME1;
+ else
+ error(ERROR, "Incorrect syntax for `defined'");
+ tp++;
+ continue;
+ }
+ if (np->flag&ISMAC)
+ builtin(trp, np->val);
+ else {
+ expand(trp, np);
+ }
+ tp = trp->tp;
+ }
+ if (flag)
+ unsetsource();
+}
+
+/*
+ * Expand the macro whose name is np, at token trp->tp, in the tokenrow.
+ * Return trp->tp at the first token next to be expanded
+ * (ordinarily the beginning of the expansion)
+ */
+void
+expand(Tokenrow *trp, Nlist *np)
+{
+ Tokenrow ntr;
+ int ntokc, narg, i;
+ Token *tp;
+ Tokenrow *atr[NARG+1];
+ int hs;
+
+ copytokenrow(&ntr, np->vp); /* copy macro value */
+ if (np->ap==NULL) /* parameterless */
+ ntokc = 1;
+ else {
+ ntokc = gatherargs(trp, atr, &narg);
+ if (narg<0) { /* not actually a call (no '(') */
+ trp->tp++;
+ return;
+ }
+ if (narg != rowlen(np->ap)) {
+ error(ERROR, "Disagreement in number of macro arguments");
+ trp->tp->hideset = newhideset(trp->tp->hideset, np);
+ trp->tp += ntokc;
+ return;
+ }
+ substargs(np, &ntr, atr); /* put args into replacement */
+ for (i=0; i<narg; i++) {
+ dofree(atr[i]->bp);
+ dofree(atr[i]);
+ }
+ }
+ doconcat(&ntr); /* execute ## operators */
+ hs = newhideset(trp->tp->hideset, np);
+ for (tp=ntr.bp; tp<ntr.lp; tp++) { /* distribute hidesets */
+ if (tp->type==NAME) {
+ if (tp->hideset==0)
+ tp->hideset = hs;
+ else
+ tp->hideset = unionhideset(tp->hideset, hs);
+ }
+ }
+ ntr.tp = ntr.bp;
+ insertrow(trp, ntokc, &ntr);
+ trp->tp -= rowlen(&ntr);
+ dofree(ntr.bp);
+}
+
+/*
+ * Gather an arglist, starting in trp with tp pointing at the macro name.
+ * Return total number of tokens passed, stash number of args found.
+ * trp->tp is not changed relative to the tokenrow.
+ */
+int
+gatherargs(Tokenrow *trp, Tokenrow **atr, int *narg)
+{
+ int parens = 1;
+ int ntok = 0;
+ Token *bp, *lp;
+ Tokenrow ttr;
+ int ntokp;
+ int needspace;
+
+ *narg = -1; /* means that there is no macro call */
+ /* look for the ( */
+ for (;;) {
+ trp->tp++;
+ ntok++;
+ if (trp->tp >= trp->lp) {
+ gettokens(trp, 0);
+ if ((trp->lp-1)->type==END) {
+ trp->lp -= 1;
+ trp->tp -= ntok;
+ return ntok;
+ }
+ }
+ if (trp->tp->type==LP)
+ break;
+ if (trp->tp->type!=NL)
+ return ntok;
+ }
+ *narg = 0;
+ ntok++;
+ ntokp = ntok;
+ trp->tp++;
+ /* search for the terminating ), possibly extending the row */
+ needspace = 0;
+ while (parens>0) {
+ if (trp->tp >= trp->lp)
+ gettokens(trp, 0);
+ if (needspace) {
+ needspace = 0;
+ makespace(trp);
+ }
+ if (trp->tp->type==END) {
+ trp->lp -= 1;
+ trp->tp -= ntok;
+ error(ERROR, "EOF in macro arglist");
+ return ntok;
+ }
+ if (trp->tp->type==NL) {
+ trp->tp += 1;
+ adjustrow(trp, -1);
+ trp->tp -= 1;
+ makespace(trp);
+ needspace = 1;
+ continue;
+ }
+ if (trp->tp->type==LP)
+ parens++;
+ else if (trp->tp->type==RP)
+ parens--;
+ trp->tp++;
+ ntok++;
+ }
+ trp->tp -= ntok;
+ /* Now trp->tp won't move underneath us */
+ lp = bp = trp->tp+ntokp;
+ for (; parens>=0; lp++) {
+ if (lp->type == LP) {
+ parens++;
+ continue;
+ }
+ if (lp->type==RP)
+ parens--;
+ if (lp->type==DSHARP)
+ lp->type = DSHARP1; /* ## not special in arg */
+ if ((lp->type==COMMA && parens==0) || (parens<0 && (lp-1)->type!=LP)) {
+ if (*narg>=NARG-1)
+ error(FATAL, "Sorry, too many macro arguments");
+ ttr.bp = ttr.tp = bp;
+ ttr.lp = lp;
+ atr[(*narg)++] = normtokenrow(&ttr);
+ bp = lp+1;
+ }
+ }
+ return ntok;
+}
+
+/*
+ * substitute the argument list into the replacement string
+ * This would be simple except for ## and #
+ */
+void
+substargs(Nlist *np, Tokenrow *rtr, Tokenrow **atr)
+{
+ Tokenrow tatr;
+ Token *tp;
+ int ntok, argno;
+
+ for (rtr->tp=rtr->bp; rtr->tp<rtr->lp; ) {
+ if (rtr->tp->type==SHARP) { /* string operator */
+ tp = rtr->tp;
+ rtr->tp += 1;
+ if ((argno = lookuparg(np, rtr->tp))<0) {
+ error(ERROR, "# not followed by macro parameter");
+ continue;
+ }
+ ntok = 1 + (rtr->tp - tp);
+ rtr->tp = tp;
+ insertrow(rtr, ntok, stringify(atr[argno]));
+ continue;
+ }
+ if (rtr->tp->type==NAME
+ && (argno = lookuparg(np, rtr->tp)) >= 0) {
+ if ((rtr->tp+1)->type==DSHARP
+ || (rtr->tp!=rtr->bp && (rtr->tp-1)->type==DSHARP))
+ insertrow(rtr, 1, atr[argno]);
+ else {
+ copytokenrow(&tatr, atr[argno]);
+ expandrow(&tatr, "<macro>");
+ insertrow(rtr, 1, &tatr);
+ dofree(tatr.bp);
+ }
+ continue;
+ }
+ rtr->tp++;
+ }
+}
+
+/*
+ * Evaluate the ## operators in a tokenrow
+ */
+void
+doconcat(Tokenrow *trp)
+{
+ Token *ltp, *ntp;
+ Tokenrow ntr;
+ int len;
+
+ for (trp->tp=trp->bp; trp->tp<trp->lp; trp->tp++) {
+ if (trp->tp->type==DSHARP1)
+ trp->tp->type = DSHARP;
+ else if (trp->tp->type==DSHARP) {
+ char tt[128];
+ ltp = trp->tp-1;
+ ntp = trp->tp+1;
+ if (ltp<trp->bp || ntp>=trp->lp) {
+ error(ERROR, "## occurs at border of replacement");
+ continue;
+ }
+ len = ltp->len + ntp->len;
+ strncpy((char*)tt, (char*)ltp->t, ltp->len);
+ strncpy((char*)tt+ltp->len, (char*)ntp->t, ntp->len);
+ tt[len] = '\0';
+ setsource("<##>", -1, tt);
+ maketokenrow(3, &ntr);
+ gettokens(&ntr, 1);
+ unsetsource();
+ if (ntr.lp-ntr.bp!=2 || ntr.bp->type==UNCLASS)
+ error(WARNING, "Bad token %r produced by ##", &ntr);
+ ntr.lp = ntr.bp+1;
+ trp->tp = ltp;
+ makespace(&ntr);
+ insertrow(trp, (ntp-ltp)+1, &ntr);
+ dofree(ntr.bp);
+ trp->tp--;
+ }
+ }
+}
+
+/*
+ * tp is a potential parameter name of macro mac;
+ * look it up in mac's arglist, and if found, return the
+ * corresponding index in the argname array. Return -1 if not found.
+ */
+int
+lookuparg(Nlist *mac, Token *tp)
+{
+ Token *ap;
+
+ if (tp->type!=NAME || mac->ap==NULL)
+ return -1;
+ for (ap=mac->ap->bp; ap<mac->ap->lp; ap++) {
+ if (ap->len==tp->len && strncmp((char*)ap->t,(char*)tp->t,ap->len)==0)
+ return ap - mac->ap->bp;
+ }
+ return -1;
+}
+
+/*
+ * Return a quoted version of the tokenrow (from # arg)
+ */
+#define STRLEN 512
+Tokenrow *
+stringify(Tokenrow *vp)
+{
+ static Token t = { STRING };
+ static Tokenrow tr = { &t, &t, &t+1, 1 };
+ Token *tp;
+ uchar s[STRLEN];
+ uchar *sp = s, *cp;
+ int i, instring;
+
+ *sp++ = '"';
+ for (tp = vp->bp; tp < vp->lp; tp++) {
+ instring = tp->type==STRING || tp->type==CCON;
+ if (sp+2*tp->len >= &s[STRLEN-10]) {
+ error(ERROR, "Stringified macro arg is too long");
+ break;
+ }
+ if (tp->wslen && (tp->flag&XPWS)==0)
+ *sp++ = ' ';
+ for (i=0, cp=tp->t; i<tp->len; i++) {
+ if (instring && (*cp=='"' || *cp=='\\'))
+ *sp++ = '\\';
+ *sp++ = *cp++;
+ }
+ }
+ *sp++ = '"';
+ *sp = '\0';
+ sp = s;
+ t.len = strlen((char*)sp);
+ t.t = newstring(sp, t.len, 0);
+ return &tr;
+}
+
+/*
+ * expand a builtin name
+ */
+void
+builtin(Tokenrow *trp, int biname)
+{
+ char *op;
+ Token *tp;
+ Source *s;
+
+ tp = trp->tp;
+ trp->tp++;
+ /* need to find the real source */
+ s = cursource;
+ while (s && s->fd==-1)
+ s = s->next;
+ if (s==NULL)
+ s = cursource;
+ /* most are strings */
+ tp->type = STRING;
+ if (tp->wslen) {
+ *outbufp++ = ' ';
+ tp->wslen = 1;
+ }
+ op = outbufp;
+ *op++ = '"';
+ switch (biname) {
+
+ case KLINENO:
+ tp->type = NUMBER;
+ op = outnum(op-1, s->line);
+ break;
+
+ case KFILE: {
+ char *src = s->filename;
+ while ((*op++ = *src++) != 0)
+ if (src[-1] == '\\')
+ *op++ = '\\';
+ op--;
+ break;
+ }
+
+ case KDATE:
+ strncpy(op, curtime+4, 7);
+ strncpy(op+7, curtime+20, 4);
+ op += 11;
+ break;
+
+ case KTIME:
+ strncpy(op, curtime+11, 8);
+ op += 8;
+ break;
+
+ default:
+ error(ERROR, "cpp botch: unknown internal macro");
+ return;
+ }
+ if (tp->type==STRING)
+ *op++ = '"';
+ tp->t = (uchar*)outbufp;
+ tp->len = op - outbufp;
+ outbufp = op;
+}
diff --git a/src/tools/lcc/cpp/nlist.c b/src/tools/lcc/cpp/nlist.c
new file mode 100644
index 0000000..d3a8357
--- /dev/null
+++ b/src/tools/lcc/cpp/nlist.c
@@ -0,0 +1,104 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cpp.h"
+
+extern char *optarg;
+extern int optind;
+extern int verbose;
+extern int Cplusplus;
+Nlist *kwdefined;
+char wd[128];
+
+#define NLSIZE 128
+
+static Nlist *nlist[NLSIZE];
+
+struct kwtab {
+ char *kw;
+ int val;
+ int flag;
+} kwtab[] = {
+ {"if", KIF, ISKW},
+ {"ifdef", KIFDEF, ISKW},
+ {"ifndef", KIFNDEF, ISKW},
+ {"elif", KELIF, ISKW},
+ {"else", KELSE, ISKW},
+ {"endif", KENDIF, ISKW},
+ {"include", KINCLUDE, ISKW},
+ {"define", KDEFINE, ISKW},
+ {"undef", KUNDEF, ISKW},
+ {"line", KLINE, ISKW},
+ {"warning", KWARNING, ISKW},
+ {"error", KERROR, ISKW},
+ {"pragma", KPRAGMA, ISKW},
+ {"eval", KEVAL, ISKW},
+ {"defined", KDEFINED, ISDEFINED+ISUNCHANGE},
+ {"__LINE__", KLINENO, ISMAC+ISUNCHANGE},
+ {"__FILE__", KFILE, ISMAC+ISUNCHANGE},
+ {"__DATE__", KDATE, ISMAC+ISUNCHANGE},
+ {"__TIME__", KTIME, ISMAC+ISUNCHANGE},
+ {"__STDC__", KSTDC, ISUNCHANGE},
+ {NULL}
+};
+
+unsigned long namebit[077+1];
+Nlist *np;
+
+void
+setup_kwtab(void)
+{
+ struct kwtab *kp;
+ Nlist *np;
+ Token t;
+ static Token deftoken[1] = {{ NAME, 0, 0, 0, 7, (uchar*)"defined" }};
+ static Tokenrow deftr = { deftoken, deftoken, deftoken+1, 1 };
+
+ for (kp=kwtab; kp->kw; kp++) {
+ t.t = (uchar*)kp->kw;
+ t.len = strlen(kp->kw);
+ np = lookup(&t, 1);
+ np->flag = kp->flag;
+ np->val = kp->val;
+ if (np->val == KDEFINED) {
+ kwdefined = np;
+ np->val = NAME;
+ np->vp = &deftr;
+ np->ap = 0;
+ }
+ }
+}
+
+Nlist *
+lookup(Token *tp, int install)
+{
+ unsigned int h;
+ Nlist *np;
+ uchar *cp, *cpe;
+
+ h = 0;
+ for (cp=tp->t, cpe=cp+tp->len; cp<cpe; )
+ h += *cp++;
+ h %= NLSIZE;
+ np = nlist[h];
+ while (np) {
+ if (*tp->t==*np->name && tp->len==np->len
+ && strncmp((char*)tp->t, (char*)np->name, tp->len)==0)
+ return np;
+ np = np->next;
+ }
+ if (install) {
+ np = new(Nlist);
+ np->vp = NULL;
+ np->ap = NULL;
+ np->flag = 0;
+ np->val = 0;
+ np->len = tp->len;
+ np->name = newstring(tp->t, tp->len, 0);
+ np->next = nlist[h];
+ nlist[h] = np;
+ quickset(tp->t[0], tp->len>1? tp->t[1]:0);
+ return np;
+ }
+ return NULL;
+}
diff --git a/src/tools/lcc/cpp/tokens.c b/src/tools/lcc/cpp/tokens.c
new file mode 100644
index 0000000..3570896
--- /dev/null
+++ b/src/tools/lcc/cpp/tokens.c
@@ -0,0 +1,370 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cpp.h"
+
+static char wbuf[2*OBS];
+static char *wbp = wbuf;
+
+/*
+ * 1 for tokens that don't need whitespace when they get inserted
+ * by macro expansion
+ */
+static const char wstab[] = {
+ 0, /* END */
+ 0, /* UNCLASS */
+ 0, /* NAME */
+ 0, /* NUMBER */
+ 0, /* STRING */
+ 0, /* CCON */
+ 1, /* NL */
+ 0, /* WS */
+ 0, /* DSHARP */
+ 0, /* EQ */
+ 0, /* NEQ */
+ 0, /* LEQ */
+ 0, /* GEQ */
+ 0, /* LSH */
+ 0, /* RSH */
+ 0, /* LAND */
+ 0, /* LOR */
+ 0, /* PPLUS */
+ 0, /* MMINUS */
+ 0, /* ARROW */
+ 1, /* SBRA */
+ 1, /* SKET */
+ 1, /* LP */
+ 1, /* RP */
+ 0, /* DOT */
+ 0, /* AND */
+ 0, /* STAR */
+ 0, /* PLUS */
+ 0, /* MINUS */
+ 0, /* TILDE */
+ 0, /* NOT */
+ 0, /* SLASH */
+ 0, /* PCT */
+ 0, /* LT */
+ 0, /* GT */
+ 0, /* CIRC */
+ 0, /* OR */
+ 0, /* QUEST */
+ 0, /* COLON */
+ 0, /* ASGN */
+ 1, /* COMMA */
+ 0, /* SHARP */
+ 1, /* SEMIC */
+ 1, /* CBRA */
+ 1, /* CKET */
+ 0, /* ASPLUS */
+ 0, /* ASMINUS */
+ 0, /* ASSTAR */
+ 0, /* ASSLASH */
+ 0, /* ASPCT */
+ 0, /* ASCIRC */
+ 0, /* ASLSH */
+ 0, /* ASRSH */
+ 0, /* ASOR */
+ 0, /* ASAND */
+ 0, /* ELLIPS */
+ 0, /* DSHARP1 */
+ 0, /* NAME1 */
+ 0, /* DEFINED */
+ 0, /* UMINUS */
+};
+
+void
+maketokenrow(int size, Tokenrow *trp)
+{
+ trp->max = size;
+ if (size>0)
+ trp->bp = (Token *)domalloc(size*sizeof(Token));
+ else
+ trp->bp = NULL;
+ trp->tp = trp->bp;
+ trp->lp = trp->bp;
+}
+
+Token *
+growtokenrow(Tokenrow *trp)
+{
+ int ncur = trp->tp - trp->bp;
+ int nlast = trp->lp - trp->bp;
+
+ trp->max = 3*trp->max/2 + 1;
+ trp->bp = (Token *)realloc(trp->bp, trp->max*sizeof(Token));
+ if (trp->bp == NULL)
+ error(FATAL, "Out of memory from realloc");
+ trp->lp = &trp->bp[nlast];
+ trp->tp = &trp->bp[ncur];
+ return trp->lp;
+}
+
+/*
+ * Compare a row of tokens, ignoring the content of WS; return !=0 if different
+ */
+int
+comparetokens(Tokenrow *tr1, Tokenrow *tr2)
+{
+ Token *tp1, *tp2;
+
+ tp1 = tr1->tp;
+ tp2 = tr2->tp;
+ if (tr1->lp-tp1 != tr2->lp-tp2)
+ return 1;
+ for (; tp1<tr1->lp ; tp1++, tp2++) {
+ if (tp1->type != tp2->type
+ || (tp1->wslen==0) != (tp2->wslen==0)
+ || tp1->len != tp2->len
+ || strncmp((char*)tp1->t, (char*)tp2->t, tp1->len)!=0)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * replace ntok tokens starting at dtr->tp with the contents of str.
+ * tp ends up pointing just beyond the replacement.
+ * Canonical whitespace is assured on each side.
+ */
+void
+insertrow(Tokenrow *dtr, int ntok, Tokenrow *str)
+{
+ int nrtok = rowlen(str);
+
+ dtr->tp += ntok;
+ adjustrow(dtr, nrtok-ntok);
+ dtr->tp -= ntok;
+ movetokenrow(dtr, str);
+ makespace(dtr);
+ dtr->tp += nrtok;
+ makespace(dtr);
+}
+
+/*
+ * make sure there is WS before trp->tp, if tokens might merge in the output
+ */
+void
+makespace(Tokenrow *trp)
+{
+ uchar *tt;
+ Token *tp = trp->tp;
+
+ if (tp >= trp->lp)
+ return;
+ if (tp->wslen) {
+ if (tp->flag&XPWS
+ && (wstab[tp->type] || (trp->tp>trp->bp && wstab[(tp-1)->type]))) {
+ tp->wslen = 0;
+ return;
+ }
+ tp->t[-1] = ' ';
+ return;
+ }
+ if (wstab[tp->type] || (trp->tp>trp->bp && wstab[(tp-1)->type]))
+ return;
+ tt = newstring(tp->t, tp->len, 1);
+ *tt++ = ' ';
+ tp->t = tt;
+ tp->wslen = 1;
+ tp->flag |= XPWS;
+}
+
+/*
+ * Copy an entire tokenrow into another, at tp.
+ * It is assumed that there is enough space.
+ * Not strictly conforming.
+ */
+void
+movetokenrow(Tokenrow *dtr, Tokenrow *str)
+{
+ int nby;
+
+ /* nby = sizeof(Token) * (str->lp - str->bp); */
+ nby = (char *)str->lp - (char *)str->bp;
+ memmove(dtr->tp, str->bp, nby);
+}
+
+/*
+ * Move the tokens in a row, starting at tr->tp, rightward by nt tokens;
+ * nt may be negative (left move).
+ * The row may need to be grown.
+ * Non-strictly conforming because of the (char *), but easily fixed
+ */
+void
+adjustrow(Tokenrow *trp, int nt)
+{
+ int nby, size;
+
+ if (nt==0)
+ return;
+ size = (trp->lp - trp->bp) + nt;
+ while (size > trp->max)
+ growtokenrow(trp);
+ /* nby = sizeof(Token) * (trp->lp - trp->tp); */
+ nby = (char *)trp->lp - (char *)trp->tp;
+ if (nby)
+ memmove(trp->tp+nt, trp->tp, nby);
+ trp->lp += nt;
+}
+
+/*
+ * Copy a row of tokens into the destination holder, allocating
+ * the space for the contents. Return the destination.
+ */
+Tokenrow *
+copytokenrow(Tokenrow *dtr, Tokenrow *str)
+{
+ int len = rowlen(str);
+
+ maketokenrow(len, dtr);
+ movetokenrow(dtr, str);
+ dtr->lp += len;
+ return dtr;
+}
+
+/*
+ * Produce a copy of a row of tokens. Start at trp->tp.
+ * The value strings are copied as well. The first token
+ * has WS available.
+ */
+Tokenrow *
+normtokenrow(Tokenrow *trp)
+{
+ Token *tp;
+ Tokenrow *ntrp = new(Tokenrow);
+ int len;
+
+ len = trp->lp - trp->tp;
+ if (len<=0)
+ len = 1;
+ maketokenrow(len, ntrp);
+ for (tp=trp->tp; tp < trp->lp; tp++) {
+ *ntrp->lp = *tp;
+ if (tp->len) {
+ ntrp->lp->t = newstring(tp->t, tp->len, 1);
+ *ntrp->lp->t++ = ' ';
+ if (tp->wslen)
+ ntrp->lp->wslen = 1;
+ }
+ ntrp->lp++;
+ }
+ if (ntrp->lp > ntrp->bp)
+ ntrp->bp->wslen = 0;
+ return ntrp;
+}
+
+/*
+ * Debugging
+ */
+void
+peektokens(Tokenrow *trp, char *str)
+{
+ Token *tp;
+
+ tp = trp->tp;
+ flushout();
+ if (str)
+ fprintf(stderr, "%s ", str);
+ if (tp<trp->bp || tp>trp->lp)
+ fprintf(stderr, "(tp offset %ld) ", (long int) (tp - trp->bp));
+ for (tp=trp->bp; tp<trp->lp && tp<trp->bp+32; tp++) {
+ if (tp->type!=NL) {
+ int c = tp->t[tp->len];
+ tp->t[tp->len] = 0;
+ fprintf(stderr, "%s", tp->t);
+ tp->t[tp->len] = c;
+ }
+ if (tp->type==NAME) {
+ fprintf(stderr, tp==trp->tp?"{*":"{");
+ prhideset(tp->hideset);
+ fprintf(stderr, "} ");
+ } else
+ fprintf(stderr, tp==trp->tp?"{%x*} ":"{%x} ", tp->type);
+ }
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+void
+puttokens(Tokenrow *trp)
+{
+ Token *tp;
+ int len;
+ uchar *p;
+
+ if (verbose)
+ peektokens(trp, "");
+ tp = trp->bp;
+ for (; tp<trp->lp; tp++) {
+ len = tp->len+tp->wslen;
+ p = tp->t-tp->wslen;
+ while (tp<trp->lp-1 && p+len == (tp+1)->t - (tp+1)->wslen) {
+ tp++;
+ len += tp->wslen+tp->len;
+ }
+ if (len>OBS/2) { /* handle giant token */
+ if (wbp > wbuf)
+ write(1, wbuf, wbp-wbuf);
+ write(1, (char *)p, len);
+ wbp = wbuf;
+ } else {
+ memcpy(wbp, p, len);
+ wbp += len;
+ }
+ if (wbp >= &wbuf[OBS]) {
+ write(1, wbuf, OBS);
+ if (wbp > &wbuf[OBS])
+ memcpy(wbuf, wbuf+OBS, wbp - &wbuf[OBS]);
+ wbp -= OBS;
+ }
+ }
+ trp->tp = tp;
+ if (cursource->fd==0)
+ flushout();
+}
+
+void
+flushout(void)
+{
+ if (wbp>wbuf) {
+ write(1, wbuf, wbp-wbuf);
+ wbp = wbuf;
+ }
+}
+
+/*
+ * turn a row into just a newline
+ */
+void
+setempty(Tokenrow *trp)
+{
+ trp->tp = trp->bp;
+ trp->lp = trp->bp+1;
+ *trp->bp = nltoken;
+}
+
+/*
+ * generate a number
+ */
+char *
+outnum(char *p, int n)
+{
+ if (n>=10)
+ p = outnum(p, n/10);
+ *p++ = n%10 + '0';
+ return p;
+}
+
+/*
+ * allocate and initialize a new string from s, of length l, at offset o
+ * Null terminated.
+ */
+uchar *
+newstring(uchar *s, int l, int o)
+{
+ uchar *ns = (uchar *)domalloc(l+o+1);
+
+ ns[l+o] = '\0';
+ return (uchar*)strncpy((char*)ns+o, (char*)s, l) - o;
+}
diff --git a/src/tools/lcc/cpp/unix.c b/src/tools/lcc/cpp/unix.c
new file mode 100644
index 0000000..ff1496a
--- /dev/null
+++ b/src/tools/lcc/cpp/unix.c
@@ -0,0 +1,134 @@
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include "cpp.h"
+
+extern int lcc_getopt(int, char *const *, const char *);
+extern char *optarg, rcsid[];
+extern int optind;
+int verbose;
+int Mflag; /* only print active include files */
+char *objname; /* "src.$O: " */
+int Cplusplus = 1;
+
+void
+setup(int argc, char **argv)
+{
+ int c, fd, i;
+ char *fp, *dp;
+ Tokenrow tr;
+ extern void setup_kwtab(void);
+ uchar *includeDirs[ NINCLUDE ] = { 0 };
+ int numIncludeDirs = 0;
+
+ setup_kwtab();
+ while ((c = lcc_getopt(argc, argv, "MNOVv+I:D:U:F:lg")) != -1)
+ switch (c) {
+ case 'N':
+ for (i=0; i<NINCLUDE; i++)
+ if (includelist[i].always==1)
+ includelist[i].deleted = 1;
+ break;
+ case 'I':
+ includeDirs[ numIncludeDirs++ ] = newstring( (uchar *)optarg, strlen( optarg ), 0 );
+ break;
+ case 'D':
+ case 'U':
+ setsource("<cmdarg>", -1, optarg);
+ maketokenrow(3, &tr);
+ gettokens(&tr, 1);
+ doadefine(&tr, c);
+ unsetsource();
+ break;
+ case 'M':
+ Mflag++;
+ break;
+ case 'v':
+ fprintf(stderr, "%s %s\n", argv[0], rcsid);
+ break;
+ case 'V':
+ verbose++;
+ break;
+ case '+':
+ Cplusplus++;
+ break;
+ default:
+ break;
+ }
+ dp = ".";
+ fp = "<stdin>";
+ fd = 0;
+ if (optind<argc) {
+ dp = basepath( argv[optind] );
+ fp = (char*)newstring((uchar*)argv[optind], strlen(argv[optind]), 0);
+ if ((fd = open(fp, 0)) <= 0)
+ error(FATAL, "Can't open input file %s", fp);
+ }
+ if (optind+1<argc) {
+ int fdo;
+#ifdef WIN32
+ fdo = creat(argv[optind+1], _S_IREAD | _S_IWRITE);
+#else
+ fdo = creat(argv[optind+1], 0666);
+#endif
+ if (fdo<0)
+ error(FATAL, "Can't open output file %s", argv[optind+1]);
+ dup2(fdo, 1);
+ }
+ if(Mflag)
+ setobjname(fp);
+ includelist[NINCLUDE-1].always = 0;
+ includelist[NINCLUDE-1].file = dp;
+
+ for( i = 0; i < numIncludeDirs; i++ )
+ appendDirToIncludeList( (char *)includeDirs[ i ] );
+
+ setsource(fp, fd, NULL);
+}
+
+
+char *basepath( char *fname )
+{
+ char *dp = ".";
+ char *p;
+ if ((p = strrchr(fname, '/')) != NULL) {
+ int dlen = p - fname;
+ dp = (char*)newstring((uchar*)fname, dlen+1, 0);
+ dp[dlen] = '\0';
+ }
+
+ return dp;
+}
+
+/* memmove is defined here because some vendors don't provide it at
+ all and others do a terrible job (like calling malloc) */
+// -- ouch, that hurts -- ln
+#ifndef __APPLE__ /* always use the system memmove() on Mac OS X. --ryan. */
+#ifdef memmove
+#undef memmove
+#endif
+void *
+memmove(void *dp, const void *sp, size_t n)
+{
+ unsigned char *cdp, *csp;
+
+ if (n<=0)
+ return dp;
+ cdp = dp;
+ csp = (unsigned char *)sp;
+ if (cdp < csp) {
+ do {
+ *cdp++ = *csp++;
+ } while (--n);
+ } else {
+ cdp += n;
+ csp += n;
+ do {
+ *--cdp = *--csp;
+ } while (--n);
+ }
+ return dp;
+}
+#endif
diff --git a/src/tools/lcc/doc/4.html b/src/tools/lcc/doc/4.html
new file mode 100644
index 0000000..0b4b36d
--- /dev/null
+++ b/src/tools/lcc/doc/4.html
@@ -0,0 +1,754 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+
+<head>
+<link HREF="mailto:drh@microsoft.com" REV="made" TITLE="David R. Hanson">
+<title>The lcc 4.1 Code-Generation Interface</title>
+</head>
+
+<body>
+
+<h1>The lcc 4.1 Code-Generation Interface</h1>
+
+<p ALIGN="LEFT"><strong><a HREF="http://www.research.microsoft.com/~cwfraser/">Christopher
+W. Fraser</a> and <a HREF="http://www.research.microsoft.com/~drh/">David R. Hanson</a>, <a
+HREF="http://www.research.microsoft.com/">Microsoft Research</a></strong></p>
+
+<h2>Contents</h2>
+
+<dir>
+ <li><a HREF="#intro">Introduction</a> </li>
+ <li><a HREF="#metrics">5.1 Type Metrics</a></li>
+ <li><a HREF="#symbols">5.3 Symbols</a> </li>
+ <li><a HREF="#operators">5.5 Dag Operators</a></li>
+ <li><a HREF="#flags">5.6 Interface Flags</a></li>
+ <li><a HREF="#definitions">5.8 Definitions</a></li>
+ <li><a HREF="#constants">5.9 Constants</a></li>
+ <li><a HREF="#upcalls">5.12 Upcalls</a></li>
+</dir>
+
+<h2><a NAME="intro">Introduction</a></h2>
+
+<p>Version 4.1 is the latest release of <a
+HREF="http://www.cs.princeton.edu/software/lcc/">lcc</a>, the ANSI C compiler described in
+our book <cite>A Retargetable C Compiler: Design and Implementation</cite>
+(Addison-Wesley, 1995, ISBN 0-8053-1670-1). This document summarizes the differences
+between the 4.1 code-generation interface and the 3.x interface described in Chap. 5 of <cite>A
+Retargetable C Compiler</cite>.</p>
+
+<p>Previous versions of lcc supported only three sizes of integers, two sizes of floats,
+and insisted that pointers fit in unsigned integers (see Sec. 5.1 of <cite>A Retargetable
+C Compiler</cite>). These assumptions simplified the compiler, and were suitable for
+32-bit architectures. But on 64-bit architectures, such as the DEC ALPHA, it's natural to
+have four sizes of integers and perhaps three sizes of floats, and on 16-bit
+architectures, 32-bit pointers don't fit in unsigned integers. Also, the 3.x constaints
+limited the use of lcc's back ends for other languages, such as Java.</p>
+
+<p>Version 4.x removes all of these restrictions: It supports any number of sizes for
+integers and floats, and the size of pointers need not be related to the size of any of
+the integer types. The major changes in the code-generation interface are:
+
+<ul>
+ <li>The number of type suffixes has been reduced to 6.</li>
+ <li>Dag operators are composed of a generic operator, a type suffix, and a size.</li>
+ <li>Unsigned variants of several operators have been added.</li>
+ <li>Several interface functions have new signatures.</li>
+</ul>
+
+<p>In addition, version 4.x is written in ANSI C and uses the standard I/O library and
+other standard C functions.</p>
+
+<p>The sections below parallel the subsections of Chap. 5 of <cite>A Retargetable C
+Compiler</cite> and summarize the differences between the 3.x and 4.x code-generation
+interface. Unaffected subsections are omitted. Page citations refer to pages in <cite>A
+Retargetable C Compiler</cite>.</p>
+
+<h2><a NAME="metrics">5.1 Type Metrics</a></h2>
+
+<p>There are now 10 metrics in an interface record:</p>
+
+<pre>Metrics charmetric;
+Metrics shortmetric;
+Metrics intmetric;
+Metrics longmetric;
+Metrics longlongmetric;
+Metrics floatmetric;
+Metrics doublemetric;
+Metrics longdoublemetric;
+Metrics ptrmetric;
+Metrics structmetric;</pre>
+
+<p>Each of these specifies the size and alignment of the corresponding type. <code>ptrmetric</code>
+describes all pointers.</p>
+
+<h2><a NAME="symbols">5.3 Symbols</a></h2>
+
+<p>The actual value of a constant is stored in the <code>u.c.v</code> field of a symbol,
+which holds a <code>Value</code>:</p>
+
+<pre>typedef union value {
+ long i;
+ unsigned long u;
+ long double d;
+ void *p;
+ void (*g)(void);
+} Value;</pre>
+
+<p>The value is stored in the appropriate field according to its type, which is given by
+the symbol's <code>type</code> field.</p>
+
+<h2><a NAME="operators">5.5 Dag Operators</a></h2>
+
+<p>The <code>op</code> field of a <code>node</code> structure holds a dag operator, which
+consists of a generic operator, a type suffix, and a size indicator. The type suffixes
+are:</p>
+
+<pre>enum {
+ F=FLOAT,
+ I=INT,
+ U=UNSIGNED,
+ P=POINTER,
+ V=VOID,
+ B=STRUCT
+};
+
+#define sizeop(n) ((n)&lt;&lt;10)</pre>
+
+<p>Given a generic operator <code>o</code>, a type suffix <code>t</code>, and a size <code>s</code>,
+a type- and size-specific operator is formed by <code>o+t+sizeop(s)</code>. For example, <code>ADD+F+sizeop(4)</code>
+forms the operator <code>ADDF4</code>, which denotes the sum of two 4-byte floats.
+Similarly, <code>ADD+F+sizeop(8)</code> forms <code>ADDF8</code>, which denotes 8-byte
+floating addition. In the 3.x code-generation interface, <code>ADDF</code> and <code>ADDD</code>
+denoted these operations. There was no size indicator in the 3.x operators because the
+type suffix supplied both a type and a size.</p>
+
+<p>Table 5.1 lists each generic operator, its valid type suffixes, and the number of <code>kids</code>
+and <code>syms</code> that it uses; multiple values for <code>kids</code> indicate
+type-specific variants. The notations in the <strong>syms</strong> column give the number
+of <code>syms</code> values and a one-letter code that suggests their uses: 1V indicates
+that <code>syms[0]</code> points to a symbol for a variable, 1C indicates that <code>syms[0]</code>
+is a constant, and 1L indicates that <code>syms[0]</code> is a label. For 1S, <code>syms[0]</code>
+is a constant whose value is a size in bytes; 2S adds <code>syms[1]</code>, which is a
+constant whose value is an alignment. For most operators, the type suffix and size
+indicator denote the type and size of operation to perform and the type and size of the
+result.</p>
+
+<table WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0">
+ <tr>
+ <td COLSPAN="6" ALIGN="CENTER"><strong>Table 5.1<img SRC="/~drh/resources/dot_clear.gif"
+ ALT="|" WIDTH="18" HEIGHT="1">Node Operators.</strong></td>
+ </tr>
+ <tr>
+ <td><strong>syms</strong></td>
+ <td><strong>kids</strong></td>
+ <td><strong>Operator</strong></td>
+ <td><strong>Type Suffixes</strong></td>
+ <td><strong>Sizes</strong></td>
+ <td><strong>Operation</strong></td>
+ </tr>
+ <tr>
+ <td>1V</td>
+ <td>0</td>
+ <td><code>ADDRF</code></td>
+ <td><code>...P..</code></td>
+ <td>p</td>
+ <td>address of a parameter</td>
+ </tr>
+ <tr>
+ <td>1V</td>
+ <td>0</td>
+ <td><code>ADDRG</code></td>
+ <td><code>...P..</code></td>
+ <td>p</td>
+ <td>address of a global</td>
+ </tr>
+ <tr>
+ <td>1V</td>
+ <td>0</td>
+ <td><code>ADDRL</code></td>
+ <td><code>...P..</code></td>
+ <td>p</td>
+ <td>address of a local</td>
+ </tr>
+ <tr>
+ <td>1C</td>
+ <td>0</td>
+ <td><code>CNST</code></td>
+ <td><code>FIUP..</code></td>
+ <td>fdx csilh p</td>
+ <td>constant</td>
+ </tr>
+ <tr ALIGN="LEFT" VALIGN="TOP">
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="1" HEIGHT="12"></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>1</td>
+ <td><code>BCOM</code></td>
+ <td><code>.IU...</code></td>
+ <td>ilh</td>
+ <td>bitwise complement</td>
+ </tr>
+ <tr>
+ <td>1S</td>
+ <td>1</td>
+ <td><code>CVF</code></td>
+ <td><code>FI....</code></td>
+ <td>fdx ilh</td>
+ <td>convert from float</td>
+ </tr>
+ <tr>
+ <td>1S</td>
+ <td>1</td>
+ <td><code>CVI</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx csilh csilhp</td>
+ <td>convert from signed integer</td>
+ </tr>
+ <tr>
+ <td>1S</td>
+ <td>1</td>
+ <td><code>CVP</code></td>
+ <td><code>..U..</code></td>
+ <td>p</td>
+ <td>convert from pointer</td>
+ </tr>
+ <tr>
+ <td>1S</td>
+ <td>1</td>
+ <td><code>CVU</code></td>
+ <td><code>.IUP..</code></td>
+ <td>csilh p</td>
+ <td>convert from unsigned integer</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>1</td>
+ <td><code>INDIR</code></td>
+ <td><code>FIUP.B</code></td>
+ <td>fdx csilh p</td>
+ <td>fetch</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>1</td>
+ <td><code>NEG</code></td>
+ <td><code>FI....</code></td>
+ <td>fdx ilh</td>
+ <td>negation</td>
+ </tr>
+ <tr>
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="1" HEIGHT="12"></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>ADD</code></td>
+ <td><code>FIUP..</code></td>
+ <td>fdx ilh ilhp p</td>
+ <td>addition</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>BAND</code></td>
+ <td><code>.IU...</code></td>
+ <td>ilh</td>
+ <td>bitwise AND</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>BOR</code></td>
+ <td><code>.IU...</code></td>
+ <td>ilh</td>
+ <td>bitwise inclusive OR</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>BXOR</code></td>
+ <td><code>.IU...</code></td>
+ <td>ilh</td>
+ <td>bitwise exclusive OR</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>DIV</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh</td>
+ <td>division</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>LSH</code></td>
+ <td><code>.IU...</code></td>
+ <td>ilh</td>
+ <td>left shift</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>MOD</code></td>
+ <td><code>.IU...</code></td>
+ <td>ilh</td>
+ <td>modulus</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>MUL</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh</td>
+ <td>multiplication</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>RSH</code></td>
+ <td><code>.IU...</code></td>
+ <td>ilh</td>
+ <td>right shift</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>2</td>
+ <td><code>SUB</code></td>
+ <td><code>FIUP..</code></td>
+ <td>fdx ilh ilhp p</td>
+ <td>subtraction</td>
+ </tr>
+ <tr>
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="1" HEIGHT="12"></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>2S</td>
+ <td>2</td>
+ <td><code>ASGN</code></td>
+ <td><code>FIUP.B</code></td>
+ <td>fdx csilh p</td>
+ <td>assignment</td>
+ </tr>
+ <tr>
+ <td>1L</td>
+ <td>2</td>
+ <td><code>EQ</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh ilhp</td>
+ <td>jump if equal</td>
+ </tr>
+ <tr>
+ <td>1L</td>
+ <td>2</td>
+ <td><code>GE</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh ilhp</td>
+ <td>jump if greater than or equal</td>
+ </tr>
+ <tr>
+ <td>1L</td>
+ <td>2</td>
+ <td><code>GT</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh ilhp</td>
+ <td>jump if greater than</td>
+ </tr>
+ <tr>
+ <td>1L</td>
+ <td>2</td>
+ <td><code>LE</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh ilhp</td>
+ <td>jump if less than or equal</td>
+ </tr>
+ <tr>
+ <td>1L</td>
+ <td>2</td>
+ <td><code>LT</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh ilhp</td>
+ <td>jump if less than</td>
+ </tr>
+ <tr>
+ <td>1L</td>
+ <td>2</td>
+ <td><code>NE</code></td>
+ <td><code>FIU...</code></td>
+ <td>fdx ilh ilhp</td>
+ <td>jump if not equal</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>2S</td>
+ <td>1</td>
+ <td><code>ARG</code></td>
+ <td><code>FIUP.B</code></td>
+ <td>fdx ilh p</td>
+ <td>argument</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>1 or 2</td>
+ <td><code>CALL</code></td>
+ <td><code>FIUPVB</code></td>
+ <td>fdx ilh p</td>
+ <td>function call</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>1</td>
+ <td><code>RET</code></td>
+ <td><code>FIUPV.</code></td>
+ <td>fdx ilh p</td>
+ <td>return from function</td>
+ </tr>
+ <tr>
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="1" HEIGHT="12"></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>1</td>
+ <td><code>JUMP</code></td>
+ <td><code>....V.</code></td>
+ <td></td>
+ <td>unconditional jump</td>
+ </tr>
+ <tr>
+ <td>1L</td>
+ <td>0</td>
+ <td><code>LABEL</code></td>
+ <td><code>....V.</code></td>
+ <td></td>
+ <td>label definition</td>
+ </tr>
+</table>
+
+<p>The entries in the <strong>Sizes</strong> column indicate sizes of the operators that
+back ends must implement. Letters denote the size of float (f), double (d), long double
+(x), character (c), short integer (s), integer (i), long integer (l), &quot;long
+long&quot; integer (h) , and pointer (p). These sizes are separated into sets for each
+type suffix, except that a single set is used for both I and U when the set for I is
+identical to the set for U.</p>
+
+<p>The actual values for the size indicators, fdxcsilhp, depend on the target. A
+specification like <code>ADDF</code>f denotes the operator <code>ADD+F+sizeop(</code>f<code>)</code>,
+where &quot;f&quot; is replaced by a target-dependent value, e.g., <code>ADDF4</code> and <code>ADDF8</code>.
+For example, back ends must implement the following <code>CVI</code> and <code>MUL</code>
+operators.</p>
+
+<blockquote>
+ <p><code>CVIF</code>f <code>CVIF</code>d <code>CVIF</code>x<br>
+ <code>CVII</code>c <code>CVII</code>s <code>CVII</code>i <code>CVII</code>l <code>CVII</code>h<br>
+ <code>CVIU</code>c <code>CVIU</code>s <code>CVIU</code>i <code>CVIU</code>l <code>CVIU</code>h
+ <code>CVIU</code>p<br>
+ <br>
+ <code>MULF</code>f <code>MULF</code>d <code>MULF</code>x<br>
+ <code>MULI</code>i <code>MULI</code>l <code>MULI</code>h<br>
+ <code>MULU</code>i <code>MULU</code>l <code>MULU</code>h</p>
+</blockquote>
+
+<p>On most platforms, there are fewer than three sizes of floats and six sizes of
+integers, and pointers are usually the same size as one of the integers. And lcc doesn't
+support the &quot;long long&quot; type, so h is not currently used. So the set of
+platform-specific operators is usually smaller than the list above suggests. For example,
+the X86, SPARC, and MIPS back ends implement the following <code>CVI</code> and <code>MUL</code>
+operators.</p>
+
+<blockquote>
+ <p><code>CVIF</code>4 <code>CVIF</code>8<br>
+ <code>CVII</code>1 <code>CVII</code>2 <code>CVII</code>4<br>
+ <code>CVIU</code>1 <code>CVIU</code>2 <code>CVIU</code>4 <br>
+ <br>
+ <code>MULF</code>4 <code>MULF</code>8<br>
+ <code>MULI</code>4<br>
+ <code>MULU</code>4</p>
+</blockquote>
+
+<p>The set of operators is thus target-dependent; for example, <code>ADDI8</code> appears
+only if the target supports an 8-byte integer type. <a
+HREF="ftp://ftp.cs.princeton.edu/pub/packages/lcc/contrib/ops.c"><code>ops.c</code></a> is
+a program that, given a set of sizes, prints the required operators and their values,
+e.g.,</p>
+
+<blockquote>
+ <pre>% <em>ops c=1 s=2 i=4 l=4 h=4 f=4 d=8 x=8 p=4</em>
+...
+ CVIF4=4225 CVIF8=8321
+ CVII1=1157 CVII2=2181 CVII4=4229
+ CVIU1=1158 CVIU2=2182 CVIU4=4230
+...
+ MULF4=4561 MULF8=8657
+ MULI4=4565
+ MULU4=4566
+...
+131 operators</pre>
+</blockquote>
+
+<p>The type suffix for a conversion operator denotes the type of the result and the size
+indicator gives the size of the result. For example, <code>CVUI4</code> converts an
+unsigned (<code>U</code>) to a 4-byte signed integer (<code>I4</code>). The <code>syms[0]</code>
+field points to a symbol-table entry for an integer constant that gives the size of the
+source operand. For example, if <code>syms[0]</code> in a <code>CVUI4</code> points to a
+symbol-table entry for 2, the conversion widens a 2-byte unsigned integer to a 4-byte
+signed integer. Conversions that widen unsigned integers zero-extend; those that widen
+signed integers sign-extend.</p>
+
+<p>The front end composes conversions between types <em>T</em><sub>1</sub> and <em>T</em><sub>2</sub>
+by widening <em>T</em><sub>1</sub> to its &quot;supertype&quot;, if necessary, converting
+that result to <em>T</em><sub>2</sub>'s supertype, then narrowing the result to <em>T</em><sub>2</sub>,
+if necessary. The following table lists the supertypes; omitted entries are their own
+supertypes.</p>
+
+<blockquote>
+ <table BORDER="0" CELLPADDING="0" CELLSPACING="0">
+ <tr>
+ <td><strong>Type</strong></td>
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="24" HEIGHT="1"></td>
+ <td><strong>Supertype</strong></td>
+ </tr>
+ <tr>
+ <td>signed char</td>
+ <td></td>
+ <td>int</td>
+ </tr>
+ <tr>
+ <td>signed short</td>
+ <td></td>
+ <td>int</td>
+ </tr>
+ <tr ALIGN="LEFT" VALIGN="TOP">
+ <td>unsigned char</td>
+ <td></td>
+ <td>int, if sizeof (char) &lt; sizeof (int)<br>
+ unsigned, otherwise</td>
+ </tr>
+ <tr ALIGN="LEFT" VALIGN="TOP">
+ <td>unsigned short</td>
+ <td></td>
+ <td>int, if sizeof (short) &lt; sizeof (int)<br>
+ unsigned, otherwise</td>
+ </tr>
+ <tr ALIGN="LEFT" VALIGN="TOP">
+ <td>void *</td>
+ <td></td>
+ <td>an unsigned type as large as a pointer</td>
+ </tr>
+ </table>
+</blockquote>
+
+<p>Pointers are converted to an unsigned type of the same size, even when that type is not
+one of the integer types.</p>
+
+<p>For example, the front end converts a signed short to a float by first converting it to
+an int and then to a float. It converts an unsigned short to an int with a single <code>CVUI</code>i
+conversion, when shorts are smaller than ints.</p>
+
+<p>There are now signed and unsigned variants of <code>ASGN</code>, <code>INDIR</code>, <code>BCOM</code>,
+<code>BOR</code>, <code>BXOR</code>, <code>BAND</code>, <code>ARG</code>, <code>CALL</code>,
+and <code>RET</code> to simplify code generation on platforms that use different
+instructions or register set for signed and unsigned operations. Likewise there are now
+pointer variants of <code>ASGN</code>, <code>INDIR</code>, <code>ARG</code>, <code>CALL</code>,
+and <code>RET</code>.</p>
+
+<h2><a NAME="flags">5.6 Interface Flags</a></h2>
+
+<pre>unsigned unsigned_char:1;</pre>
+
+<p>tells the front end whether plain characters are signed or unsigned. If it's zero, char
+is a signed type; otherwise, char is an unsigned type.</p>
+
+<p>All the interface flags can be set by command-line options, e.g., <code>-Wf-unsigned_char=1</code>
+causes plain characters to be unsigned.</p>
+
+<h2><a NAME="definitions">5.8 Definitions</a></h2>
+
+<p>The front end announces local variables by calling</p>
+
+<pre>void (*local)(Symbol);</pre>
+
+<p>It announces temporaries likewise; these have the symbol's <code>temporary</code> flag
+set, which indicates that the symbol will be used only in the next call to <code>gen</code>.
+If a temporary's <code>u.t.cse</code> field is nonnull, it points to the node that
+computes the value assigned to the temporary; see page 346.</p>
+
+<p>The front end calls</p>
+
+<pre>void (*address)(Symbol p, Symbol q, long n);</pre>
+
+<p>to initialize <code>q</code> to a symbol that represents an address of the form <em>x</em>+<code>n</code>,
+where <em>x</em> is the address represented by <code>p</code> and the long integer <code>n</code>
+is positive or negative.</p>
+
+<h2><a NAME="constants">5.9 Constants</a></h2>
+
+<p>The interface function</p>
+
+<pre>void (*defconst)(int suffix, int size, Value v);</pre>
+
+<p>initializes constants. defconst emits directives to define a cell and initialize it to
+a constant value. v is the constant value, suffix identifies the type of the value, and
+size is the size of the value in bytes. The value of suffix indicates which field of v
+holds the value, as shown in the following table.</p>
+
+<blockquote>
+ <table BORDER="0" CELLPADDING="1" CELLSPACING="1">
+ <tr>
+ <td><strong>suffix</strong></td>
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="24" HEIGHT="1"></td>
+ <td><strong>v Field</strong></td>
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="24" HEIGHT="1"></td>
+ <td><strong>size</strong></td>
+ </tr>
+ <tr>
+ <td><code>F</code></td>
+ <td></td>
+ <td><code>v.d</code></td>
+ <td></td>
+ <td>float, double, long double</td>
+ </tr>
+ <tr>
+ <td><code>I</code></td>
+ <td></td>
+ <td><code>v.i</code></td>
+ <td></td>
+ <td>signed char, signed short, signed int, signed long</td>
+ </tr>
+ <tr>
+ <td><code>U</code></td>
+ <td></td>
+ <td><code>v.u</code></td>
+ <td></td>
+ <td>unsigned char, unsigned short, unsigned int, unsigned long</td>
+ </tr>
+ <tr>
+ <td><code>P</code></td>
+ <td></td>
+ <td><code>v.p</code></td>
+ <td></td>
+ <td>void *</td>
+ </tr>
+ </table>
+</blockquote>
+
+<p><code>defconst</code> must narrow <code>v.</code>x when <code>size</code> is less than <code>sizeof</code>
+<code>v.</code>x; e.g., to emit an unsigned char, <code>defconst</code> should emit <code>(unsigned
+char)v.i</code>.</p>
+
+<h2><a NAME="upcalls">5.12 Upcalls</a></h2>
+
+<p>lcc 4.x uses standard I/O and its I/O functions have been changed accordingly. lcc
+reads input from the standard input, emits code to the standard output, and writes
+diagnostics to the standard error output. It uses <code>freopen</code> to redirect these
+streams to explicit files, when necessary.</p>
+
+<p><code>bp</code>, <code>outflush</code>, and <code>outs</code> have been eliminated.</p>
+
+<pre>extern void fprint(FILE *f, const char *fmt, ...);
+extern void print(const char *fmt, ...);</pre>
+
+<p>print formatted data to file <code>f</code> (<code>fprint</code>) or the standard
+output (<code>print</code>). These functions are like standard C's <code>printf</code> and
+<code>fprintf</code>, but support only some of the standard conversion specifiers and do
+not support flags, precision, and field-width specifications. They support the following
+new conversion specifiers in addition to those described on page 99.</p>
+
+<blockquote>
+ <table BORDER="0" CELLPADDING="0" CELLSPACING="0">
+ <tr>
+ <td><strong>Specifiers</strong></td>
+ <td><img SRC="/~drh/resources/dot_clear.gif" ALT="|" WIDTH="24" HEIGHT="1"></td>
+ <td><strong>Corresponding printf Specifiers</strong></td>
+ </tr>
+ <tr>
+ <td><code>%c</code></td>
+ <td></td>
+ <td><code>%c</code></td>
+ </tr>
+ <tr>
+ <td><code>%d %D</code></td>
+ <td></td>
+ <td><code>%d %ld</code></td>
+ </tr>
+ <tr>
+ <td><code>%u %U</code></td>
+ <td></td>
+ <td><code>%u %lu</code></td>
+ </tr>
+ <tr>
+ <td><code>%x %X</code></td>
+ <td></td>
+ <td><code>%x %lx</code></td>
+ </tr>
+ <tr>
+ <td><code>%f %e %g</code></td>
+ <td></td>
+ <td><code>%e %f %g</code></td>
+ </tr>
+ <tr ALIGN="LEFT" VALIGN="TOP">
+ <td><code>%p</code></td>
+ <td></td>
+ <td>Converts the corresponding void * argument to unsigned long and prints it with the <code>printf</code>
+ <code>%#x</code> specifier or just <code>%x</code> when the argument is null.</td>
+ </tr>
+ <tr ALIGN="LEFT" VALIGN="TOP">
+ <td><code>%I</code></td>
+ <td></td>
+ <td>Prints the number of spaces given by the corresponding argument.</td>
+ </tr>
+ </table>
+</blockquote>
+
+<pre>#define generic(op) ((op)&amp;0x3F0)
+#define specific(op) ((op)&amp;0x3FF)</pre>
+
+<p><code>generic(op)</code> returns the generic variant of <code>op</code>; that is,
+without its type suffix and size indicator. <code>specific(op)</code> returns the
+type-specific variant of <code>op</code>; that is, without its size indicator.</p>
+
+<p><code>newconst</code> has been replaced by</p>
+
+<pre>extern Symbol intconst(int n);</pre>
+
+<p>which installs the integer constant <code>n</code> in the symbol table, if necessary,
+and returns a pointer to the symbol-table entry.</p>
+
+<hr>
+
+<address>
+ <a HREF="http://www.research.microsoft.com/~cwfraser/">Chris Fraser</a> / <a
+ HREF="mailto:cwfraser@microsoft.com">cwfraser@microsoft.com</a><br>
+ <a HREF="http://www.research.microsoft.com/~drh/">David Hanson</a> / <a
+ HREF="mailto:drh@microsoft.com">drh@microsoft.com</a><br>
+ $Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $
+</address>
+</body>
+</html>
diff --git a/src/tools/lcc/doc/bprint.1 b/src/tools/lcc/doc/bprint.1
new file mode 100644
index 0000000..8cf9971
--- /dev/null
+++ b/src/tools/lcc/doc/bprint.1
@@ -0,0 +1,83 @@
+.\" $Id: bprint.1 145 2001-10-17 21:53:10Z timo $
+.TH BPRINT 1 "local \- $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $"
+.SH NAME
+bprint \- expression profiler
+.SH SYNOPSIS
+.B bprint
+[
+.I option ...
+]
+[
+.I file ...
+]
+.SH DESCRIPTION
+.I bprint
+produces on the standard output a listing of the programs compiled by
+.I lcc
+with the
+.B \-b
+option.
+Executing an
+.B a.out
+so compiled appends profiling data to
+.BR prof.out .
+The first token of each expression in the listing is preceded
+by the number of times it was executed
+enclosed in angle brackets as determined from the data in
+.BR prof.out .
+.I bprint
+interprets the following options.
+.TP
+.B \-c
+Compress the
+.B prof.out
+file, which otherwise grows with every execution of
+.BR a.out .
+.TP
+.B \-b
+Print an annotated listing as described above.
+.TP
+.B \-n
+Include line numbers in the listing.
+.TP
+.B \-f
+Print only the number of invocations of each function.
+A second
+.B \-f
+summarizes call sites instead of callers.
+.TP
+.BI \-I \*Sdir
+specifies additional directories in which to seek
+files given in
+.B prof.out
+that do not begin with `/'.
+.PP
+If any file names are given, only the requested data for those files are printed
+in the order presented.
+If no options are given,
+.B \-b
+is assumed.
+.SH FILES
+.PP
+.ta \w'$LCCDIR/liblcc.{a,lib}XX'u
+.nf
+prof.out profiling data
+$LCCDIR/liblcc.{a,lib} \fIlcc\fP-specific library
+.SH "SEE ALSO"
+.IR lcc (1),
+.IR prof (1)
+.SH BUGS
+Macros and comments can confuse
+.I bprint
+because it uses post-expansion source coordinates
+to annotate pre-expansion source files.
+If
+.I bprint
+sees that it's about to print a statement count
+.I inside
+a number or identifier, it moves the count to just
+.I before
+the token.
+.PP
+Can't cope with an ill-formed
+.BR prof.out .
diff --git a/src/tools/lcc/doc/bprint.pdf b/src/tools/lcc/doc/bprint.pdf
new file mode 100644
index 0000000..1b11963
--- /dev/null
+++ b/src/tools/lcc/doc/bprint.pdf
Binary files differ
diff --git a/src/tools/lcc/doc/install.html b/src/tools/lcc/doc/install.html
new file mode 100644
index 0000000..3410e8f
--- /dev/null
+++ b/src/tools/lcc/doc/install.html
@@ -0,0 +1,796 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+
+<head>
+<link HREF="mailto:drh@cs.princeton.edu" REV="made" TITLE="David R. Hanson">
+<title>Installing lcc</title>
+</head>
+
+<body>
+
+<h1>Installing lcc</h1>
+
+<p ALIGN="LEFT"><strong><a HREF="http://www.research.microsoft.com/~cwfraser/">Christopher
+W. Fraser</a> and <a HREF="http://www.research.microsoft.com/~drh/">David R. Hanson</a>, <a
+HREF="http://www.research.microsoft.com/">Microsoft Research</a></strong></p>
+
+<h2>Contents</h2>
+
+<dir>
+ <li><a HREF="#intro">Introduction</a></li>
+ <li><a HREF="#unix">Installation on UNIX</a></li>
+ <li><a HREF="#driver">Building the Driver</a></li>
+ <li><a HREF="#rcc">Building the Compiler and Accessories</a></li>
+ <li><a HREF="#win32">Installation on Windows NT 4.0 and Windows 95/98</a></li>
+ <li><a HREF="#bugs">Reporting Bugs</a></li>
+ <li><a HREF="#mailinglist">Keeping in Touch</a></li>
+</dir>
+
+<h2><a NAME="intro">Introduction</a></h2>
+
+<p><a HREF="http://www.cs.princeton.edu/software/lcc/">lcc</a> is the ANSI C compiler
+described in our book <cite>A Retargetable C Compiler: Design and Implementation</cite>
+(Addison-Wesley, 1995, ISBN 0-8053-1670-1).</p>
+
+<p>If you're installing lcc on a UNIX system, read the remainder of this section and
+continue with the next section. If you're installing lcc on a Windows NT 4.0 or Windows
+95/98 system, and you intend only to <u>use</u> lcc, you can run the <a
+href="ftp://ftp.cs.princeton.edu/pub/packages/lcc/lcc41.exe">InstallShield executable</a>,
+which installs the binaries and the documentation. If you want to <u>modify</u> lcc or <u>rebuild</u>
+it from the source files, you need the <a
+href="ftp://ftp.cs.princeton.edu/packages/lcc/lcc41.zip">complete distribution</a>, and
+you should read the rest of the section, the following three sections, and the <a
+HREF="#win32">Windows NT/95/98</a> section.</p>
+
+<p>Extract the distribution into its own directory. All non-absolute paths below are
+relative to this directory. The distribution holds the following subdirectories.</p>
+
+<blockquote>
+ <table BORDER="0" CELLPADDING="1" CELLSPACING="1" WIDTH="80%">
+ <tr>
+ <td><a HREF="../src"><code>src</code></a></td>
+ <td></td>
+ <td>source code</td>
+ </tr>
+ <tr>
+ <td><a HREF="../etc"><code>etc</code></a></td>
+ <td></td>
+ <td>driver, accessories</td>
+ </tr>
+ <tr>
+ <td><a HREF="../lib"><code>lib</code></a></td>
+ <td></td>
+ <td>runtime library source code</td>
+ </tr>
+ <tr>
+ <td><a HREF="../cpp"><code>cpp</code></a></td>
+ <td></td>
+ <td>preprocessor source code</td>
+ </tr>
+ <tr>
+ <td><a HREF="../lburg"><code>lburg</code></a></td>
+ <td></td>
+ <td>code-generator generator source code</td>
+ </tr>
+ <tr>
+ <td><a HREF="../doc"><code>doc</code></a></td>
+ <td></td>
+ <td>this document, man pages</td>
+ </tr>
+ <tr>
+ <td><code><a HREF="../include">include</a>/*/*</code></td>
+ <td></td>
+ <td>include files</td>
+ </tr>
+ <tr>
+ <td><a HREF="../tst"><code>tst</code></a></td>
+ <td></td>
+ <td>test suite</td>
+ </tr>
+ <tr>
+ <td><code><a HREF="../alpha">alpha</a>/*/tst</code></td>
+ <td></td>
+ <td>ALPHA test outputs</td>
+ </tr>
+ <tr>
+ <td><code><a HREF="../mips">mips</a>/*/tst</code></td>
+ <td></td>
+ <td>MIPS test outputs</td>
+ </tr>
+ <tr>
+ <td><code><a HREF="../sparc">sparc</a>/*/tst</code></td>
+ <td></td>
+ <td>SPARC test outputs</td>
+ </tr>
+ <tr>
+ <td><code><a HREF="../x86">x86</a>/*/tst</code></td>
+ <td></td>
+ <td>X86 test outputs</td>
+ </tr>
+ </table>
+</blockquote>
+
+<p><code>doc/install.html</code> is the HTML file for this document. <a HREF="4.html"><code>doc/4.html</code></a>
+describes the internal differences between lcc 3.x and 4.1.</p>
+
+<p>The installation makefile is designed so that lcc can be installed from a read-only
+file system or directory, which is common in networked environments, so the distribution
+can be unloaded on a central file server. <strong>You will need an existing ANSI/ISO C
+compiler to build and install lcc.</strong></p>
+
+<h2><a NAME="unix">Installation on UNIX</a></h2>
+
+<p>The compilation components (the preprocessor, include files, and compiler proper, etc.)
+are installed in a single <em>build directory</em>. On multi-platform systems supported by
+a central file server, it's common to store the build directory in a location specific to
+the platform and to the version of lcc, and to point a symbolic link to this location. For
+example,</p>
+
+<blockquote>
+ <pre>% ln -s /usr/local/lib/lcc-4.1/sparc-solaris /usr/local/lib/lcc</pre>
+</blockquote>
+
+<p>points <code>/usr/local/lib/lcc</code> to a build directory for lcc version 4.1 on the
+SPARC under Solaris. Links into <code>/usr/local/lib</code> are created for the programs <code>lcc</code>
+and <code>bprint</code>. Thus, a new distribution can be installed by building it in its
+own build directory and changing one symbolic link to point to that directory. If these
+conventions or their equivalents are followed, the host-specific parts of the driver
+program, <code>lcc</code>, can be used unmodified.</p>
+
+<p>Installation on a UNIX system involves the following steps. Below, the build directory
+is referred to as <code>BUILDDIR</code>.
+
+<ol>
+ <li>Create the build directory, using a version- and platform-specific naming convention as
+ suggested above, and record the name of this directory in the <code>BUILDDIR</code>
+ environment variable:<blockquote>
+ <pre>% setenv BUILDDIR /usr/local/lib/lcc-4.1/sparc-solaris
+% mkdir -p $BUILDDIR</pre>
+ </blockquote>
+ <p>Here and below, commands assume the C shell. Also, you'll need a version of <code>mkdir</code>
+ that supports the <code>-p</code> option, which creates intermediate directories as
+ necessary.</p>
+ </li>
+ <li>Copy the man pages to the repository for local man pages, e.g.,<blockquote>
+ <pre>% cp doc/*.1 /usr/local/man/man1</pre>
+ </blockquote>
+ <p>Some users copy the man pages to the build directory and create the appropriate
+ symbolic links, e.g., </p>
+ <blockquote>
+ <pre>% cp doc/*.1 $BUILDDIR
+% ln -s $BUILDDIR/*.1 /usr/local/man/man1</pre>
+ </blockquote>
+ </li>
+ <li>Platform-specific include files are in directories named <code>include/</code><em>target</em><code>/</code><em>os</em>.
+ Create the include directory in the build directory, and copy the include hierarchy for
+ your platform to this directory, e.g.,<blockquote>
+ <pre>% mkdir $BUILDDIR/include
+% cp -p -R include/sparc/solaris/* $BUILDDIR/include</pre>
+ </blockquote>
+ <p>Again, some users create a symbolic link to the appropriate directory in the
+ distribution instead of copying the include files. For example, at Princeton, the
+ distributions are stored under <code>/proj/pkg/lcc</code>, so the included files are
+ &quot;installed&quot; by creating one symbolic link: </p>
+ <blockquote>
+ <pre>% ln -s /proj/pkg/lcc/4.1/include/sparc/solaris $BUILDDIR/include</pre>
+ </blockquote>
+ <p>If you're installing lcc on Linux, you <em>must</em> also plant a symbolic link named <code>gcc</code>
+ to gcc's library directory, because lcc uses gcc's C preprocessor and most of gcc's header
+ files:</p>
+ <blockquote>
+ <pre>% ln -s /usr/lib/gcc-lib/i486-linux/2.7.2.2 $BUILDDIR/gcc</pre>
+ </blockquote>
+ <p>The library directory shown above may be different on your Linux machine; to determine
+ the correct directory, browse <code>/usr/lib/gcc-lib</code>, or execute</p>
+ <blockquote>
+ <pre>% cc -v tst/8q.c</pre>
+ </blockquote>
+ <p>and examine the diagnostic output. Make sure that <code>$BUILDDIR/gcc/cpp</code> and <code>$BUILDDIR/gcc/include</code>
+ point to, respectively, gcc's C preprocessor and header files. On Linux, lcc looks for
+ include files in <code>$BUILDDIR/include</code>, <code>$BUILDDIR/gcc/include</code>, and <code>/usr/include</code>,
+ in that order; see <a HREF="#driver"><em>Building the Driver</em></a> and <a
+ href="../etc/linux.c"><code>etc/linux.c</code></a> for details.</p>
+ </li>
+ <li>The <a HREF="../makefile"><code>makefile</code></a> includes the file named by the <code>CUSTOM</code>
+ macro; the default is <code>custom.mk</code>, and an empty <code>custom.mk</code> is
+ included in the distribution. If desired, prepare a site-specification customization file
+ and define <code>CUSTOM</code> to the path of that file when invoking make in steps 5 and
+ 6, e.g.,<blockquote>
+ <pre>make CUSTOM=/users/drh/solaris.mk</pre>
+ </blockquote>
+ <p>You can, for example, use customization files to record site-specific values for macros
+ instead of using environment variables, and to record targets for the steps in this list.</p>
+ </li>
+ <li>Build the host-specific driver, creating a custom host-specific part, if necessary. See <a
+ HREF="#driver"><em>Building the Driver</em></a>.</li>
+ <li>Build the preprocessor, compiler proper, library, and other accessories. See <a
+ HREF="#rcc"><em>Building the Compiler</em></a>.</li>
+ <li>Plant symbolic links to the build directory and to the installed programs, e.g.,<blockquote>
+ <pre>% ln -s $BUILDDIR /usr/local/lib/lcc
+% ln -s /usr/local/lib/{lcc,bprint} /usr/local/bin</pre>
+ </blockquote>
+ <p>Some users copy <code>bprint</code> and <code>lcc</code> into <code>/usr/local/bin</code>
+ instead of creating symbolic links. The advantange of creating the links for <code>lcc</code>
+ and <code>bprint</code> as shown is that, once established, they point indirectly to
+ whatever <code>/usr/local/lib/lcc</code> points to; installing a new version of lcc, say,
+ 4.2, can be done by changing <code>/usr/local/lib/lcc</code> to point to the 4.2 build
+ directory.</p>
+ </li>
+</ol>
+
+<h2><a NAME="driver">Building the Driver</a></h2>
+
+<p>The preprocessor, compiler, assembler, and loader are invoked by a driver program, <code>lcc</code>,
+which is similar to <code>cc</code> on most systems. It's described in the man page <code>doc/lcc.1</code>.
+The driver is built by combining the host-independent part, <a href="../etc/lcc.c"><code>etc/lcc.c</code></a>,
+with a small host-specific part. Distributed host-specific parts are named <code>etc/</code><em>os</em><code>.c</code>,
+where <em>os</em> is the name of the operating system for the host on which <code>lcc</code>
+is being installed. If you're following the installations conventions described above, you
+can probably use one of the host-specific parts unmodified; otherwise, pick one that is
+closely related to your platform, copy it to <em>whatever</em><code>.c</code>, and edit it
+as described below. You should not have to edit <code>etc/lcc.c</code>.</p>
+
+<p>We'll use <a HREF="../etc/solaris.c"><code>etc/solaris.c</code></a> as an example in
+describing how the host-specific part works. This example illustrates all the important
+features. Make sure you have the environment variable <code>BUILDDIR</code> set correctly,
+and build the driver with a <code>make</code> command, e.g.,</p>
+
+<blockquote>
+ <pre>% make HOSTFILE=etc/solaris.c lcc
+cc -g -c -DTEMPDIR=\&quot;/tmp\&quot; -o /usr/local/lib/lcc-4.1/sparc-solaris/lcc.o etc/lcc.c
+cc -g -c -o /usr/local/lib/lcc-4.1/sparc-solaris/host.o etc/solaris.c
+cc -g -o /usr/local/lib/lcc-4.1/sparc-solaris/lcc /usr/local/lib/lcc-4.1/sparc-solaris/lcc.o /usr/local/lib/lcc-4.1/sparc-solaris/host.o</pre>
+</blockquote>
+
+<p>The symbolic name <code>HOSTFILE</code> specifies the path to the host-specific part,
+either one in the distribution or <em>whatever</em><code>.c</code>. Some versions of make
+may require the <code>-e</code> option in order to read the environment.</p>
+
+<p>Here's <code>etc/solaris.c</code>:</p>
+
+<blockquote>
+ <pre>/* Sparcs running Solaris 2.5.1 at CS Dept., Princeton University */
+
+#include &lt;string.h&gt;
+
+static char rcsid[] = &quot;$ Id: solaris.c,v 1.10 1998/09/14 20:36:33 drh Exp $&quot;;
+
+#ifndef LCCDIR
+#define LCCDIR &quot;/usr/local/lib/lcc/&quot;
+#endif
+#ifndef SUNDIR
+#define SUNDIR &quot;/opt/SUNWspro/SC4.2/lib/&quot;
+#endif
+
+char *suffixes[] = { &quot;.c&quot;, &quot;.i&quot;, &quot;.s&quot;, &quot;.o&quot;, &quot;.out&quot;, 0 };
+char inputs[256] = &quot;&quot;;
+char *cpp[] = { LCCDIR &quot;cpp&quot;,
+ &quot;-D__STDC__=1&quot;, &quot;-Dsparc&quot;, &quot;-D__sparc__&quot;, &quot;-Dsun&quot;, &quot;-D__sun__&quot;, &quot;-Dunix&quot;,
+ &quot;$1&quot;, &quot;$2&quot;, &quot;$3&quot;, 0 };
+char *include[] = { &quot;-I&quot; LCCDIR &quot;include&quot;, &quot;-I/usr/local/include&quot;,
+ &quot;-I/usr/include&quot;, 0 };
+char *com[] = { LCCDIR &quot;rcc&quot;, &quot;-target=sparc/solaris&quot;,
+ &quot;$1&quot;, &quot;$2&quot;, &quot;$3&quot;, 0 };
+char *as[] = { &quot;/usr/ccs/bin/as&quot;, &quot;-Qy&quot;, &quot;-s&quot;, &quot;-o&quot;, &quot;$3&quot;, &quot;$1&quot;, &quot;$2&quot;, 0 };
+char *ld[] = { &quot;/usr/ccs/bin/ld&quot;, &quot;-o&quot;, &quot;$3&quot;, &quot;$1&quot;,
+ SUNDIR &quot;crti.o&quot;, SUNDIR &quot;crt1.o&quot;,
+ SUNDIR &quot;values-xa.o&quot;, &quot;$2&quot;, &quot;&quot;,
+ &quot;-Y&quot;, &quot;P,&quot; SUNDIR &quot;:/usr/ccs/lib:/usr/lib&quot;, &quot;-Qy&quot;,
+ &quot;-L&quot; LCCDIR, &quot;-llcc&quot;, &quot;-lm&quot;, &quot;-lc&quot;, SUNDIR &quot;crtn.o&quot;, 0 };
+
+extern char *concat(char *, char *);
+
+int option(char *arg) {
+ if (strncmp(arg, &quot;-lccdir=&quot;, 8) == 0) {
+ cpp[0] = concat(&amp;arg[8], &quot;/cpp&quot;);
+ include[0] = concat(&quot;-I&quot;, concat(&amp;arg[8], &quot;/include&quot;));
+ ld[12] = concat(&quot;-L&quot;, &amp;arg[8]);
+ com[0] = concat(&amp;arg[8], &quot;/rcc&quot;);
+ } else if (strcmp(arg, &quot;-p&quot;) == 0) {
+ ld[5] = SUNDIR &quot;mcrt1.o&quot;;
+ ld[10] = &quot;P,&quot; SUNDIR &quot;libp:/usr/ccs/lib/libp:/usr/lib/libp:&quot;
+ SUNDIR &quot;:/usr/ccs/lib:/usr/lib&quot;;
+ } else if (strcmp(arg, &quot;-b&quot;) == 0)
+ ;
+ else if (strncmp(arg, &quot;-ld=&quot;, 4) == 0)
+ ld[0] = &amp;arg[4];
+ else
+ return 0;
+ return 1;
+}</pre>
+</blockquote>
+
+<p><code>LCCDIR</code> defaults to <code>&quot;/usr/local/lib/lcc/&quot;</code> unless
+it's defined by a <code>-D</code> option as part of <code>CFLAGS</code> in the make
+command, e.g.,</p>
+
+<blockquote>
+ <pre>% make HOSTFILE=etc/solaris.c CFLAGS='-DLCCDIR=\&quot;/v/lib/lcc/\&quot;' lcc</pre>
+</blockquote>
+
+<p>Note the trailing slash; <code>SUNDIR</code> is provided so you can use <code>etc/solaris.c</code>
+even if you have a different version of the Sun Pro compiler suite. If you're using the
+gcc compiler tools instead of the Sun Pro tools, see <a HREF="../etc/gcc-solaris.c"><code>etc/gcc-solaris.c</code></a>.</p>
+
+<p>Most of the host-specific code is platform-specific data and templates for the commands
+that invoke the preprocessor, compiler, assembler, and loader. The <code>suffixes</code>
+array lists the file name suffixes for C source files, preprocessed source files, assembly
+language source files, object files, and executable files. <code>suffixes</code> must be
+terminated with a null pointer, as shown above. The initialization of <code>suffixes</code>
+in <code><a HREF="../etc/solaris.c">etc/solaris.c</a></code> are the typical ones for UNIX
+systems. Each element of <code>suffixes</code> is actually a list of suffixes, separated
+by semicolons; <code><a HREF="../etc/win32.c">etc/win32.c</a></code> holds an example:</p>
+
+<blockquote>
+ <pre>char *suffixes[] = { &quot;.c;.C&quot;, &quot;.i;.I&quot;, &quot;.asm;.ASM;.s;.S&quot;, &quot;.obj;.OBJ&quot;, &quot;.exe&quot;, 0 };</pre>
+</blockquote>
+
+<p>When a list is given, the first suffix is used whenever lcc needs to generate a file
+name. For example, with <code><a HREF="../etc/win32.c">etc/win32.c</a></code>, lcc emits
+the generated assembly code into <code>.asm</code> files.</p>
+
+<p>The <code>inputs</code> array holds a null-terminated string of directories separated
+by colons or semicolons. These are used as the default value of <code>LCCINPUTS</code>, if
+the environment variable <code>LCCINPUTS</code> is not set; see the <a HREF="lcc.pdf">man
+page</a>.</p>
+
+<p>Each command template is an array of pointers to strings terminated with a null
+pointer; the strings are full path names of commands, arguments, or argument placeholders,
+which are described below. Commands are executed in a child process, and templates can
+contain multiple commands by separating commands with newlines. The driver runs each
+command in a new process.</p>
+
+<p>The <code>cpp</code> array gives the command for running lcc's preprocessor, <code>cpp</code>.
+Literal arguments specified in templates, e.g., <code>&quot;-Dsparc&quot;</code> in the <code>cpp</code>
+command above, are passed to the command as given.</p>
+
+<p>The strings <code>&quot;$1&quot;</code>, <code>&quot;$2&quot;</code>, and <code>&quot;$3&quot;</code>
+in templates are placeholders for <em>lists</em> of arguments that are substituted in a
+copy of the template before the command is executed. <code>$1</code> is replaced by the <em>options</em>
+specified by the user; for the preprocessor, this list always contains at least <code>-D__LCC__</code>.
+<code>$2</code> is replaced by the <em>input</em> files, and <code>$3</code> is replaced
+by the <em>output</em> file.</p>
+
+<p>Zero-length arguments after replacement are removed from the argument list before the
+command is invoked. So, for example, if the preprocessor is invoked without an output
+file, <code>&quot;$3&quot;</code> becomes <code>&quot;&quot;</code>, which is removed from
+the final argument list.</p>
+
+<p>The <code>include</code> array is a list of <code>-I</code> options that specify which
+directives should be searched to satisfy include directives. These directories are
+searched in the order given. The first directory should be the one to which the ANSI
+header files were copied as described in <a HREF="#unix">UNIX</a> or <a HREF="#win32">Windows</a>
+installation instructions. The driver adds these options to <code>cpp</code>'s arguments
+when it invokes the preprocessor, except when <code>-N</code> is specified.</p>
+
+<p><code>com</code> gives the command for invoking the compiler. This template can appear
+as shown above in a custom host-specific part, but the option <code>-target=sparc/solaris</code>
+should be edited to the <em>target</em><code>/</code><em>os</em> for your platform. If <code>com[1]</code>
+includes the string &quot;<code>win32</code>&quot;, the driver assumes it's running on
+Windows. lcc can generate code for <em>all</em> of the <em>target</em><code>/</code><em>os</em>
+combinations listed in the file <code>src/bind.c</code>. The <code>-target</code> option
+specifies the default combination. The driver's <code>-Wf</code> option can be used to
+specify other combinations; the <a HREF="lcc.pdf">man page</a> elaborates.</p>
+
+<p><code>as</code> gives the command for invoking the assembler. On Linux, you must be
+running at least version 2.8.1 of the GNU assembler; earlier versions mis-assemble some
+instructions emitted by lcc.</p>
+
+<p><code>ld</code> gives the command for invoking the loader. For the other commands, the
+list <code>$2</code> contains a single file; for <code>ld</code>, <code>$2</code> contains
+all &quot;.o&quot; files and libraries, and <code>$3</code> is <code>a.out</code>, unless
+the <code>-o</code> option is specified. As suggested in the code above, <code>ld</code>
+must also specify the appropriate startup code and default libraries, including the lcc
+library, <code>liblcc.a</code>.</p>
+
+<p>The <code>option</code> function is described below; the minimal <code>option</code>
+function just returns 0.</p>
+
+<p>You can test <code>lcc</code> with the options <code>-v -v</code> to display the
+commands that would be executed, e.g.,</p>
+
+<blockquote>
+ <pre>% $BUILDDIR/lcc -v -v foo.c baz.c mylib.a -lX11
+/usr/local/lib/lcc-4.1/lcc $ Id: solaris.c,v 1.10 1998/09/14 20:36:33 drh Exp $
+foo.c:
+/usr/local/lib/lcc/cpp -D__STDC__=1 -Dsparc -D__sparc__ -Dsun -D__sun__ -Dunix -D__LCC__ -I/usr/local/lib/lcc/include -I/usr/local/include -I/usr/include foo.c /tmp/lcc266290.i
+/usr/local/lib/lcc/rcc -target=sparc/solaris -v /tmp/lcc266290.i /tmp/lcc266291.
+s
+/usr/ccs/bin/as -Qy -s -o /tmp/lcc266292.o /tmp/lcc266291.s
+baz.c:
+/usr/local/lib/lcc/cpp -D__STDC__=1 -Dsparc -D__sparc__ -Dsun -D__sun__ -Dunix -D__LCC__ -I/usr/local/lib/lcc/include -I/usr/local/include -I/usr/include baz.c /tmp/lcc266290.i
+/usr/local/lib/lcc/rcc -target=sparc/solaris -v /tmp/lcc266290.i /tmp/lcc266291.s
+/usr/ccs/bin/as -Qy -s -o /tmp/lcc266293.o /tmp/lcc266291.s
+/usr/ccs/bin/ld -o a.out /opt/SUNWspro/SC4.2/lib/crti.o /opt/SUNWspro/SC4.2/lib/crt1.o /opt/SUNWspro/SC4.2/lib/values-xa.o /tmp/lcc266292.o /tmp/lcc266293.o mylib.a -lX11 -Y P,/opt/SUNWspro/SC4.2/lib/:/usr/ccs/lib:/usr/lib -Qy -L/usr/local/lib/lcc/ -llcc -lm -lc /opt/SUNWspro/SC4.2/lib/crtn.o
+rm /tmp/lcc266293.o /tmp/lcc266290.i /tmp/lcc266291.s /tmp/lcc266292.o</pre>
+</blockquote>
+
+<p>As the output shows, <code>lcc</code> places temporary files in <code>/tmp</code>; if
+any of the environment variables <code>TMP</code>, <code>TEMP</code>, and <code>TMPDIR</code>
+are set, they override this default (in the order shown) as does the <code>-tempdir=</code><em>dir</em>
+option. The default can be changed by defining <code>TEMPDIR</code> in <code>CFLAGS</code>
+when building the driver.</p>
+
+<p>The <code>option</code> function is called for the options <code>-Wo</code>, <code>-g</code>,
+<code>-p</code>, <code>-pg</code>, and <code>-b</code> because these compiler options
+might also affect the loader's arguments. For these options, the driver calls <code>option(arg)</code>
+to give the host-specific code an opportunity to edit the <code>ld</code> command, if
+necessary. <code>option</code> can change <code>ld</code>, if necessary, and return 1 to
+announce its acceptance of the option. If the option is unsupported, <code>option</code>
+should return 0.</p>
+
+<p>For example, in response to <code>-g</code>, the <code>option</code> function shown
+above accepts the option but does nothing else, because the <code>ld</code> and <code>as</code>
+commands don't need to be modified on the SPARC. <code>-g</code> will also be added to the
+compiler's options by the host-independent part of the driver. The <code>-p</code> causes <code>option</code>
+to change the name of the startup code and changed the list of libraries. The <code>-b</code>
+option turns on <code>lcc</code>'s per-expression profiling, the code for which is in <code>liblcc.a</code>,
+so <code>option</code> need no nothing.</p>
+
+<p>On SPARCs, the driver also recognizes <code>-Bstatic</code> and <code>-Bdynamic</code>
+as linker options. The driver recognizes but ignores &quot;<code>-target</code> <em>name</em>&quot;
+option.</p>
+
+<p>The option <code>-Wo</code><em>arg</em> causes the driver to pass <em>arg</em> to <code>option</code>.
+Such options have no other effect; this mechanism is provided to support system-specific
+options that affect the commands executed by the driver. As illustrated above,
+host-specific parts should support the <code>-Wo-lccdir=</code><em>dir</em> option, which
+causes lcc's compilation components to be found in <em>dir</em>, because this option is
+used by the test scripts, and because the driver simulates a <code>-Wo-lccdir</code>
+option with the value of the environment variable <code>LCCDIR</code>, if it's defined.
+The code above rebuilds the paths to the include files, preprocessor, compiler, and
+library by calling <code>concat</code>, which is defined in <code>etc/lcc.c</code>.</p>
+
+<h2><a NAME="rcc">Building the Compiler and Accessories</a></h2>
+
+<p>To build the rest of compilation components make sure <code>BUILDDIR</code> is set
+appropriately and type &quot;<code>make all</code>&quot;. This command builds <code>librcc.a</code>
+(the compiler's private library), <code>rcc</code> (the compiler proper), <code>lburg</code>
+(the code-generator generator), <code>cpp</code> (the preprocessor), <code>liblcc.a</code>
+(the runtime library), and <code>bprint</code> (the profile printer), all in <code>BUILDDIR</code>.
+There may be warnings, but there should be no errors. If you're using an ANSI/ISO compiler
+other than <code>cc</code>, specify its name with the <code>CC=</code> option, e.g.,
+&quot;<code>make CC=gcc all</code>&quot;. If you're running on a DEC ALPHA, use &quot;<code>make
+CC='cc -std1' all</code>&quot;; the <code>-std1</code> option is essential on the ALPHA.
+If you're on a DEC 5000 running Ultrix 4.3, use &quot;<code>make CC=c89 all</code>&quot;.</p>
+
+<p>Once <code>rcc</code> is built with the host C compiler, run the test suite to verify
+that <code>rcc</code> is working correctly. If any of the steps below fail, contact us
+(see <a HREF="#bugs"><em>Reporting Bugs</em></a>). The commands in the makefile run the
+shell script <code>src/run.sh</code> on each C program in the test suite, <code>tst/*.c</code>.
+It uses the driver, <code>$BUILDDIR/lcc</code>, so you must have the driver in the build
+directory before testing <code>rcc</code>. The <em>target</em><code>/</code><em>os</em>
+combination is read from the variable <code>TARGET</code>, which must be specified when
+invoking <code>make</code>:</p>
+
+<blockquote>
+ <pre>% make TARGET=sparc/solaris test
+mkdir -p /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/8q.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/array.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/cf.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/cq.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/cvt.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/fields.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/front.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/incr.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/init.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/limits.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/paranoia.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/sort.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/spill.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/stdarg.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/struct.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/switch.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/wf1.s:
+/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/yacc.s:</pre>
+</blockquote>
+
+<p>Each line in the output above is of the form</p>
+
+<blockquote>
+ <p><code>$BUILDDIR/rcc -target=</code><em>target</em><code>/</code><em>os</em><code>$BUILDDIR/</code><em>target</em><code>/</code><em>os</em><code>/</code><em>X</em><code>.s:</code></p>
+</blockquote>
+
+<p>where <em>X</em> is the base name of the C program <em>X</em><code>.c</code> in the
+test suite. This output identifies the compiler and the target, e.g., &quot;<code>$BUILDDIR/rcc</code>
+is generating code for a <code>sparc</code> running the <code>solaris</code> operating
+system.&quot;</p>
+
+<p>For each program in the test suite, <code>src/run.sh</code> compiles the program, drops
+the generated assembly language code in <code>BUILDDIR</code>/<em>target</em><code>/</code><em>os</em>,
+and uses <code>diff</code> to compare the generated assembly code with the expected code
+(the code expected for <code>tst/8q.c</code> on the SPARC under Solaris is in <code>sparc/solaris/tst/8q.sbk</code>,
+etc.). If there are differences, the script executes the generated code with the input
+given in <code>tst</code> (the input for <code>tst/8q.c</code> is in <code>tst/8q.0</code>,
+etc.) and compares the output with the expected output (the expected output from <code>tst/8q.c</code>
+on the SPARC under Solaris is in <code>sparc/solaris/tst/8q.1bk</code>, etc.). The script
+also compares the diagnostics from the compiler with the expected diagnostics.</p>
+
+<p>On some systems, there may be a few differences between the generated code and the
+expected code. These differences occur because the expected code is generated by cross
+compilation and the least significant bits of some floating-point constants differ from
+those bits in constants generated on your system. On Linux, there may be differences
+because of differences in the header files between our system and yours. There should be
+no differences in the output from executing the test programs.</p>
+
+<p>Next, run the &quot;triple test&quot;, which builds <code>rcc</code> using itself:</p>
+
+<blockquote>
+ <pre>% make triple
+/usr/local/lib/lcc-4.1/sparc-solaris/lcc -o /usr/local/lib/lcc-4.1/sparc-solaris/1rcc -d0.6 -Wo-lccdir=/usr/local/lib/lcc-4.1/sparc-solaris -B/usr/local/lib/lcc-4.1/sparc-solaris/ -Isrc src/*.c
+src/alloc.c:
+...
+src/x86.c:
+/usr/local/lib/lcc-4.1/sparc-solaris/lcc -o /usr/local/lib/lcc-4.1/sparc-solaris/1rcc -d0.6 -Wo-lccdir=/usr/local/lib/lcc-4.1/sparc-solaris -B/usr/local/lib/lcc-4.1/sparc-solaris/ -Isrc src/*.c
+src/alloc.c:
+...
+src/x86.c:
+strip /usr/local/lib/lcc-4.1/sparc-solaris/[12]rcc
+dd if=/usr/local/lib/lcc-4.1/sparc-solaris/1rcc of=/usr/local/lib/lcc-4.1/sparc-solaris/rcc1 bs=512 skip=1
+769+1 records in
+769+1 records out
+dd if=/usr/local/lib/lcc-4.1/sparc-solaris/2rcc of=/usr/local/lib/lcc-4.1/sparc-solaris/rcc2 bs=512 skip=1
+769+1 records in
+769+1 records out
+if cmp /usr/local/lib/lcc-4.1/sparc-solaris/rcc[12]; then \
+ mv /usr/local/lib/lcc-4.1/sparc-solaris/2rcc /usr/local/lib/lcc-4.1/sparc-solaris/rcc; \
+ rm -f /usr/local/lib/lcc-4.1/sparc-solaris/1rcc /usr/local/lib/lcc-4.1/sparc-solaris/rcc[12]; fi</pre>
+</blockquote>
+
+<p>This command builds <code>rcc</code> twice; once using the <code>rcc</code> built by <code>cc</code>
+and again using the <code>rcc</code> built by <code>lcc</code>. The resulting binaries are
+compared. They should be identical, as shown at the end of the output above. If they
+aren't, our compiler is generating incorrect code; <a HREF="#bugs">contact</a> us.</p>
+
+<p>The final version of <code>rcc</code> should also pass the test suite; that is, the
+output from</p>
+
+<blockquote>
+ <pre>% make TARGET=sparc/solaris test</pre>
+</blockquote>
+
+<p>should be identical to that from the previous <code>make test</code>.</p>
+
+<p>The command &quot;<code>make clean</code>&quot; cleans up, but does not remove <code>rcc</code>,
+etc., and &quot;<code>make clobber</code>&quot; cleans up and removes <code>lcc</code>, <code>rcc</code>,
+and the other accessories. Test directories under <code>BUILDDIR</code> are <em>not</em>
+removed; you'll need to remove these by hand, e.g.,</p>
+
+<blockquote>
+ <pre>% rm -fr $BUILDDIR/sparc</pre>
+</blockquote>
+
+<p>The code generators for the other targets can be tested by specifying the desired <em>target</em><code>/</code><em>os</em>
+and setting an environment variable that controls what <code>src/run.sh</code> does. For
+example, to test the MIPS code generator, type</p>
+
+<blockquote>
+ <pre>% setenv REMOTEHOST noexecute
+% make TARGET=mips/irix test</pre>
+</blockquote>
+
+<p>As above, <code>src/run.sh</code> compares the MIPS code generated with what's
+expected. There should be no differences. Setting <code>REMOTEHOST</code> to <code>noexecute</code>
+suppresses the assembly and execution of the generated code. If you set <code>REMOTEHOST</code>
+to the name of a MIPS machine to which you can <code>rlogin</code>, <code>src/run.sh</code>
+will <code>rcp</code> the generated code to that machine and execute it there, if
+necessary. See <code>src/run.sh</code> for the details.</p>
+
+<p>You can use lcc as a cross compiler. The options <code>-S</code> and <code>-Wf-target=</code><em>target/os</em>
+generate assembly code for the specified target, which is any of those listed in the file <code>src/bind.c</code>.
+For example, </p>
+
+<blockquote>
+ <pre>% lcc -Wf-target=mips/irix -S tst/8q.c</pre>
+</blockquote>
+
+<p>generates MIPS code for <code>tst/8q.c</code> in <code>8q.s</code>.</p>
+
+<p>lcc can also generate code for a &quot;symbolic&quot; target. This target is used
+routinely in front-end development, and its output is a printable representation of the
+input program, e.g., the dags constructed by the front end are printed, and other
+interface functions print their arguments. You can specify this target with the option <code>-Wf-target=symbolic</code>.
+For example,</p>
+
+<blockquote>
+ <pre>% lcc -Wf-target=symbolic -S tst/8q.c</pre>
+</blockquote>
+
+<p>generates symbolic output for <code>tst/8q.c</code> in <code>8q.s</code>. Adding <code>-Wf-html</code>
+causes the symbolic target to emit HTML instead of plain text. Finally, the option <code>-Wf-target=null</code>
+specifies the &quot;null&quot; target for which lcc emits nothing and thus only checks the
+syntax and semantics of its input files.</p>
+
+<h2><a NAME="win32">Installation on Windows NT 4.0 or Windows 95/98</a></h2>
+
+<p>On Windows NT 4.0 and Windows 95/98, lcc is designed to work with Microsoft's Visual
+C++ 5.0 (VC) and Microsoft's Assembler, MASM 6.11d. It uses the VC header files,
+libraries, and command-line tools, and it uses MASM to assemble the code it generates. If
+you have MASM 6.11, make sure you <a
+HREF="http://support.microsoft.com/support/kb/articles/Q138/9/83.asp">upgrade to 6.11d</a>,
+because earlier 6.11 releases do not generate correct COFF object files.</p>
+
+<p>Building the distribution components from the ground up requires Microsoft's Visual
+C/C++ 5.0 compiler, Microsoft's make, <code>nmake</code>, and the standard Windows command
+interpreter. <a HREF="../makefile.nt"><code>makefile.nt</code></a> is written to use only <code>nmake</code>.
+As on UNIX systems, the compilation components are installed in a single <em>build
+directory</em>, and the top-level programs, <code>lcc.exe</code> and <code>bprint.exe</code>,
+are installed in a directory on the PATH. If the conventions used below are followed, the
+Windows-specific parts of the driver program, <code>lcc.exe</code>, can be used
+unmodified.</p>
+
+<p>Building from the source distribution on a Windows system involves the following steps.
+Below, the build directory is referred to as <code>BUILDDIR</code>, and the distribution
+is in <code>\dist\lcc\4.1</code>.
+
+<ol>
+ <li>Create the build directory, perhaps using a version- and platform-specific naming
+ convention as suggested in <a HREF="#unix"><em>Installation on UNIX</em></a>, and record
+ the name of this directory in the <code>BUILDDIR</code> environment variable:<blockquote>
+ <pre>C:\dist\lcc\4.1&gt;set BUILDDIR=\progra~1\lcc\4.1\bin
+C:\dist\lcc\4.1&gt;mkdir %BUILDDIR%</pre>
+ </blockquote>
+ <p>The default build, or installation, directory is <code>\Program Files\lcc\4.1\bin</code>,
+ but the <code>nmake</code> commands require that you use the corresponding 8.3 file name, <code>progra~1</code>,
+ instead of <code>Program Files</code>.</p>
+ </li>
+ <li><a HREF="../etc/win32.c"><code>etc\win32.c</code></a> is the Windows-specific part of
+ the driver. It assumes that environment variable <code>include</code> gives the locations
+ of the VC header files and that the linker (<code>link.exe</code>) and the assembler (<code>ml.exe</code>)
+ are on the PATH. It also assumes that the macro <code>LCCDIR</code> gives the build
+ directory. If necessary, revise a copy of <a HREF="../etc/win32.c"><code>etc\win32.c</code></a>
+ to reflect the conventions on your computer (see <a HREF="#driver"><em>Building the Driver</em></a>),
+ then build the driver, specifying the default temporary directory, if necessary:<blockquote>
+ <pre>C:\dist\lcc\4.1&gt;nmake -f makefile.nt TEMPDIR=\\temp HOSTFILE=etc/win32.c lcc
+...
+ cl -nologo -Zi -MLd -Fd\progra~1\lcc\4.1\bin\ -c -DTEMPDIR=\&quot;\\temp\&quot; -Fo\progra~1\lcc\4.1\bin\lcc.obj etc/lcc.c
+lcc.c
+ cl -nologo -Zi -MLd -Fd\progra~1\lcc\4.1\bin\ -c -Fo\progra~1\lcc\4.1\bin\host.obj etc/win32.c
+win32.c
+ cl -nologo -Zi -MLd -Fd\progra~1\lcc\4.1\bin\ -Fe\progra~1\lcc\4.1\bin\lcc.exe \progra~1\lcc\4.1\bin\lcc.obj \progra~1\lcc\4.1\bin\host.obj</pre>
+ </blockquote>
+ <p>If you make a copy of <code>etc\win32.c</code>, specify the path of the copy as the
+ value of <code>HOSTFILE</code>. For example, if you copy <code>etc\win32.c</code> to <code>BUILDDIR</code>
+ and edit it, use the command</p>
+ <blockquote>
+ <pre>C:\dist\lcc\4.1&gt;nmake -f makefile.nt TEMPDIR=\\temp HOSTFILE=%BUILDDIR%\win32.c lcc</pre>
+ </blockquote>
+ </li>
+ <li>Build the preprocessor, compiler proper, library, and other accessories (see <a
+ HREF="#rcc"><em>Building the Compiler</em></a>):<blockquote>
+ <pre>C:\dist\lcc\4.1&gt;nmake -f makefile.nt all</pre>
+ </blockquote>
+ <p>This command uses the VC command-line tools <code>cl</code> and <code>lib</code> to
+ build <code>bprint.exe</code>, <code>cpp.exe</code>, <code>lburg.exe</code>, <code>liblcc.lib</code>,
+ <code>librcc.lib</code>, and <code>rcc.exe</code>, all in <code>BUILDDIR</code>. There may
+ be some warnings, but there should be no warnings.</p>
+ </li>
+ <li>Create a test directory and run the test suite:<blockquote>
+ <pre>C:\dist\lcc\4.1&gt;mkdir %BUILDDIR%\x86\win32\tst
+C:\dist\lcc\4.1&gt;nmake -f makefile.nt test</pre>
+ </blockquote>
+ <p>This command compiles each program in <a HREF="../tst">tst</a>, compares the generated
+ assembly code and diagnostics with the expected assembly code and diagnostics, executes
+ the program, and compares the output with the expected output (using <code>fc</code>). For
+ example, when the nmake command compiles <a HREF="../tst/8q.c"><code>tst\8q.c</code></a>,
+ it leaves the generated assembly code and diagnostic output in <code>%BUILDDIR%\x86\win32\tst\8q.s</code>
+ and <code>%BUILDDIR%\x86\win32\tst\8q.2</code>, and it compares them with the expected
+ results in <code>x86\win32\tst\8q.sbk</code>. It builds the executable program in <code>%BUILDDIR%\x86\win32\tst\8q.exe</code>,
+ runs it, and redirects the output to <code>%BUILDDIR%\x86\win32\tst\8q.1</code>, which it
+ compares with <code>x86\win32\tst\8q.1bk</code>. The output from this step is voluminous,
+ but there should be no differences and no errors.</p>
+ </li>
+ <li>Run the &quot;triple&quot; test, which compiles <code>rcc</code> with itself and
+ verifies the results:<blockquote>
+ <pre>C:\dist\lcc\4.1&gt;nmake -f makefile.nt triple
+...
+\progra~1\lcc\4.1\bin\x86.c:
+ Assembling: C:/TEMP/lcc2001.asm
+ fc /b \progra~1\lcc\4.1\bin\1rcc.exe \progra~1\lcc\4.1\bin\2rcc.exe
+Comparing files \progra~1\lcc\4.1\bin\1rcc.exe and \progra~1\lcc\4.1\bin\2RCC.EXE
+00000088: B4 D5</pre>
+ </blockquote>
+ <p>This command builds <code>rcc</code> twice; once using the <code>rcc</code> built by VC
+ and again using the <code>rcc</code> built by <code>lcc</code>. The resulting binaries are
+ compared using <code>fc</code>. They should be identical, except for one or two bytes of
+ timestamp data, as shown at the end of the output above. If they aren't, our compiler is
+ generating incorrect code; <a HREF="#bugs">contact</a> us.</p>
+ </li>
+ <li>Copy <code>lcc.exe</code> and <code>bprint.exe</code> to a directory on your PATH, e.g.,<blockquote>
+ <pre>C:\dist\lcc\4.1&gt;copy %BUILDDIR%\lcc.exe \bin
+ 1 file(s) copied.
+
+C:\dist\lcc\4.1&gt;copy %BUILDDIR%\bprint.exe \bin
+ 1 file(s) copied.</pre>
+ </blockquote>
+ </li>
+ <li>Finally, clean up:<blockquote>
+ <pre>C:\dist\lcc\4.1&gt;nmake -f makefile.nt clean</pre>
+ </blockquote>
+ <p>This command removes the derived files in <code>BUILDDIR</code>, but does not remove <code>rcc.exe</code>,
+ etc.; &quot;<code>nmake -f makefile.nt clobber</code>&quot; cleans up and removes all
+ executables and libraries. Test directories under <code>BUILDDIR</code> are <em>not</em>
+ removed; you'll need to remove these by hand, e.g.,</p>
+ <blockquote>
+ <pre>C:\dist\lcc\4.1&gt;rmdir %BUILDDIR%\x86 /s
+\progra~1\lcc\4.1\bin\x86, Are you sure (Y/N)? y</pre>
+ </blockquote>
+ </li>
+</ol>
+
+<h2><a NAME="bugs">Reporting Bugs</a></h2>
+
+<p>lcc is a large, complex program. We find and repair errors routinely. If you think that
+you've found an error, follow the steps below, which are adapted from the instructions in
+Chapter 1 of <cite>A Retargetable C Compiler: Design and Implementation</cite>.
+
+<ol>
+ <li>If you don't have a source file that displays the error, create one. Most errors are
+ exposed when programmers try to compile a program they think is valid, so you probably
+ have a demonstration program already.</li>
+ <li>Preprocess the source file and capture the preprocessor output. Discard the original
+ code.</li>
+ <li>Prune your source code until it can be pruned no more without sending the error into
+ hiding. We prune most error demonstrations to fewer than five lines.</li>
+ <li>Confirm that the source file displays the error with the <em>distributed</em> version of
+ lcc. If you've changed lcc and the error appears only in your version, then you'll have to
+ chase the error yourself, even if it turns out to be our fault, because we can't work on
+ your code.</li>
+ <li>Annotate your code with comments that explain why you think that lcc is wrong. If lcc
+ dies with an assertion failure, please tell us where it died. If lcc crashes, please
+ report the last part of the call chain if you can. If lcc is rejecting a program you think
+ is valid, please tell us why you think it's valid, and include supporting page numbers in
+ the ANSI Standard, Appendix A in <cite>The C Programming Language</cite>, or the
+ appropriate section in <cite>C: A Reference Manual</cite>, 4th edition by S. B. Harbison
+ and G. L. Steele, Jr. (Prentice Hall, 1995). If lcc silently generates incorrect code for
+ some construct, please include the corrupt assembly code in the comments and flag the
+ incorrect instructions if you can.</li>
+ <li>Confirm that your error hasn't been fixed already. The latest version of lcc is always
+ available for anonymous <code>ftp</code> from <code>ftp.cs.princeton.edu</code> in <a
+ HREF="ftp://ftp.cs.princeton.edu/pub/lcc"><code>pub/lcc</code></a>. A <a
+ HREF="ftp://ftp.cs.princeton.edu/pub/lcc/README"><code>README</code></a> file there gives
+ acquistion details, and the <a HREF="../LOG"><code>LOG</code></a> file reports what errors
+ were fixed and when they were fixed. If you report an error that's been fixed, you might
+ get a canned reply.</li>
+ <li>Send your program by electronic mail to <code>lcc-bugs@cs.princeton.edu</code>. Please
+ send only valid C programs; put all remarks in C comments so that we can process reports
+ semiautomatically.</li>
+</ol>
+
+<h2><a NAME="mailinglist">Keeping in Touch</a></h2>
+
+<p>There is an lcc mailing list for general information about lcc. To be added to the
+list, send a message with the 1-line body</p>
+
+<blockquote>
+ <pre>subscribe lcc</pre>
+</blockquote>
+
+<p>to <code>majordomo@cs.princeton.edu</code>. This line must appear in the message body;
+&quot;Subject:&quot; lines are ignored. To learn more about mailing lists served by <code>majordomo</code>,
+send a message with the 1-word body &quot;<code>help</code>&quot; to <code>majordomo@cs.princeton.edu</code>.
+Mail sent to <code>lcc@cs.princeton.edu</code> is forwarded to everyone on the mailing
+list.</p>
+
+<p>There is also an <code>lcc-bugs</code> mailing list for reporting bugs; subscribe to it
+by sending a message with the 1-line body </p>
+
+<blockquote>
+ <pre>subscribe lcc-bugs</pre>
+</blockquote>
+
+<p>to <code>majordomo@cs.princeton.edu</code>. Mail addressed to <var>lcc-bugs@cs.princeton.edu</var>
+is forwarded to everyone on this list.</p>
+
+<hr>
+
+<address>
+ <a HREF="http://www.research.microsoft.com/~cwfraser/">Chris Fraser</a> / <a
+ HREF="mailto:cwfraser@microsoft.com">cwfraser@microsoft.com</a><br>
+ <a HREF="http://www.research.microsoft.com/~drh/">David Hanson</a> / <a
+ HREF="mailto:drh@microsoft.com">drh@microsoft.com</a><br>
+ $Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $
+</address>
+</body>
+</html>
diff --git a/src/tools/lcc/doc/lcc.1 b/src/tools/lcc/doc/lcc.1
new file mode 100644
index 0000000..fe91bca
--- /dev/null
+++ b/src/tools/lcc/doc/lcc.1
@@ -0,0 +1,605 @@
+.\" $Id: lcc.1 145 2001-10-17 21:53:10Z timo $
+.TH LCC 1 "local \- $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $"
+.SH NAME
+lcc \- ANSI C compiler
+.SH SYNOPSIS
+.B lcc
+[
+.I option
+|
+.I file
+]...
+.br
+.SH DESCRIPTION
+.PP
+.I lcc
+is an ANSI C compiler for a variety of platforms.
+.PP
+Arguments whose names end with `.c' (plus `.C' under Windows) are taken to be
+C source programs; they are preprocessed, compiled, and
+each object program is left on the file
+whose name is that of the source with `.o' (UNIX) or `.obj' (Windows)
+substituted for the extension.
+Arguments whose names end with `.i' are treated similarly,
+except they are not preprocessed.
+In the same way,
+arguments ending with `.s' (plus `.S', `.asm', and `.ASM', under Windows)
+are taken to be assembly source programs
+and are assembled, producing an object file.
+If there are no arguments,
+.I lcc
+summarizes its options on the standard error.
+.PP
+.I lcc
+deletes an object file if and only if exactly one
+source file is mentioned and no other file
+(source, object, library) or
+.B \-l
+option is mentioned.
+.PP
+If the environment variable
+.B LCCINPUTS
+is set,
+.I lcc
+assumes it gives a semicolon- or colon-separated list of directories in which to
+look for source and object files whose names do not begin with `/'.
+These directories are also added to the list of directories
+searched for libraries.
+If
+.B LCCINPUTS
+is defined, it must contain `.' in order for the current directory
+to be searched for input files.
+.PP
+.I lcc
+uses ANSI standard header files (see `FILES' below).
+Include files not found in the ANSI header files
+are taken from the normal default include areas,
+which usually includes
+.BR /usr/include .
+Under Windows, if the environment variable
+.B include
+is defined, it gives a semicolon-separated list of directories in which to search for
+header files.
+.PP
+.I lcc
+interprets the following options; unrecognized options are
+taken as loader options (see
+.IR ld (1))
+unless
+.BR \-c ,
+.BR \-S ,
+or
+.B \-E
+precedes them.
+Except for
+.BR \-l ,
+all options are processed before any of the files
+and apply to all of the files.
+Applicable options are passed to each compilation phase in the order given.
+.TP
+.B \-c
+Suppress the loading phase of the compilation, and force
+an object file to be produced even if only one program is compiled.
+.TP
+.B \-g
+Produce additional symbol table information for the local debuggers.
+.I lcc
+warns when
+.B \-g
+is unsupported.
+.TP
+.BI \-Wf\-g n , x
+Set the debugging level to
+.I n
+and emit source code as comments into the generated assembly code;
+.I x
+must be the assembly language comment character.
+If
+.I n
+is omitted, it defaults to 1, which is similar to
+.BR \-g .
+Omitting
+.BI , x
+just sets the debugging level to
+.IR n .
+.TP
+.B \-w
+Suppress warning diagnostics, such as those
+announcing unreferenced statics, locals, and parameters.
+The line
+.I
+#pragma ref id
+simulates a reference to the variable
+.IR id .
+.TP
+.BI \-d n
+Generate jump tables for switches whose density is at least
+.IR n ,
+a floating point constant between zero and one.
+The default is 0.5.
+.TP
+.B \-A
+Warns about
+declarations and casts of function types without prototypes,
+assignments between pointers to ints and pointers to enums, and
+conversions from pointers to smaller integral types.
+A second
+.B \-A
+warns about
+unrecognized control lines,
+nonANSI language extensions and source characters in literals,
+unreferenced variables and static functions,
+declaring arrays of incomplete types,
+and exceeding
+.I some
+ANSI environmental limits, like more than 257 cases in switches.
+It also arranges for duplicate global definitions in separately compiled
+files to cause loader errors.
+.TP
+.B \-P
+Writes declarations for all defined globals on standard error.
+Function declarations include prototypes;
+editing this output can simplify conversion to ANSI C.
+This output may not correspond to the input when
+there are several typedefs for the same type.
+.TP
+.B \-n
+Arrange for the compiler to produce code
+that tests for dereferencing zero pointers.
+The code reports the offending file and line number and calls
+.IR abort (3).
+.TP
+.B \-O
+is ignored.
+.TP
+.B \-S
+Compile the named C programs, and leave the
+assembler-language output on corresponding files suffixed `.s' or `.asm'.
+.TP
+.B \-E
+Run only the preprocessor on the named C programs
+and unsuffixed file arguments,
+and send the result to the standard output.
+.TP
+.BI \-o " output"
+Name the output file
+.IR output .
+If
+.B \-c
+or
+.B \-S
+is specified and there is exactly one source file,
+this option names the object or assembly file, respectively.
+Otherwise, this option names the final executable
+file generated by the loader, and `a.out' (UNIX) or `a.exe' (Windows) is left undisturbed.
+.I lcc
+warns if
+.B \-o
+and
+.B \-c
+or
+.B \-S
+are given with more than one source file and ignores the
+.B \-o
+option.
+.TP
+.BI \-D name=def
+Define the
+.I name
+to the preprocessor, as if by `#define'.
+If
+.I =def
+is omitted, the name is defined as "1".
+.TP
+.BI \-U name
+Remove any initial definition of
+.IR name .
+.TP
+.BI \-I dir
+`#include' files
+whose names do not begin with `/' are always
+sought first in the directory of the
+.I file
+arguments, then in directories named in
+.B \-I
+options, then in directories on a standard list.
+.TP
+.B \-N
+Do not search
+.I any
+of the standard directories for `#include' files.
+Only those directories specified by subsequent explicit
+.B \-I
+options will be searched, in the order given.
+.TP
+.BI \-B str
+Use the compiler
+.BI "" str rcc
+instead of the default version.
+Note that
+.I str
+often requires a trailing slash.
+On Sparcs only,
+.B \-Bstatic
+and
+.BI \-Bdynamic
+are passed to the loader; see
+.IR ld (1).
+.TP
+.BI \-Wo\-lccdir= dir
+Find the preprocessor, compiler proper, and include directory
+in the directory
+.I dir/
+or
+.I
+dir\\.
+If the environment variable
+.B LCCDIR
+is defined, it gives this directory.
+.I lcc
+warns when this option is unsupported.
+.TP
+.B \-Wf-unsigned_char=1
+.br
+.ns
+.TP
+.B \-Wf-unsigned_char=0
+makes plain
+.B char
+an unsigned (1) or signed (0) type; by default,
+.B char
+is signed.
+.TP
+.B \-Wf\-wchar_t=unsigned_char
+.br
+.ns
+.TP
+.B \-Wf\-wchar_t=unsigned_short
+.br
+.ns
+.TP
+.B \-Wf\-wchar_t=unsigned_int
+Makes wide characters the type indicated; by default,
+wide characters are unsigned short ints, and
+.B wchar_t
+is a typedef for unsigned short defined in stddef.h.
+The definition for
+.B wchar_t
+in stddef.h must correspond to the type specified.
+.TP
+.B \-v
+Print commands as they are executed; some of the executed
+programs are directed to print their version numbers.
+More than one occurrence of
+.B \-v
+causes the commands to be printed, but
+.I not
+executed.
+.TP
+.BR \-help " or " \-?
+Print a message on the standard error summarizing
+.IR lcc 's
+options and giving the values of the environment variables
+.B LCCINPUTS
+and
+.BR LCCDIR ,
+if they are defined.
+Under Windows, the values of
+.B include
+and
+.B lib
+are also given, if they are defined.
+.TP
+.B \-b
+Produce code that counts the number of times each expression is executed.
+If loading takes place, arrange for a
+.B prof.out
+file to be written when the object program terminates.
+A listing annotated with execution counts can then be generated with
+.IR bprint (1).
+.I lcc
+warns when
+.B \-b
+is unsupported.
+.B \-Wf\-C
+is similar, but counts only the number of function calls.
+.TP
+.B \-p
+Produce code that counts the number of times each function is called.
+If loading takes place, replace the standard startup
+function by one that automatically calls
+.IR monitor (3)
+at the start and arranges to write a
+.B mon.out
+file when the object program terminates normally.
+An execution profile can then be generated with
+.IR prof (1).
+.I lcc
+warns when
+.B \-p
+is unsupported.
+.TP
+.B \-pg
+Causes the compiler to produce counting code like
+.BR \-p ,
+but invokes a run-time recording mechanism that keeps more
+extensive statistics and produces a
+.B gmon.out
+file at normal termination.
+Also, a profiling library is searched, in lieu of the standard C library.
+An execution profile can then be generated with
+.IR gprof (1).
+.I lcc
+warns when
+.B \-pg
+is unsupported.
+.TP
+.BI \-t name
+.br
+.ns
+.TP
+.BI \-t
+Produce code to print the name of the function, an activation number,
+and the name and value of each argument at function entry.
+At function exit, produce code to print
+the name of the function, the activation number, and the return value.
+By default,
+.I printf
+does the printing; if
+.I name
+appears, it does.
+For null
+.I char*
+values, "(null)" is printed.
+.BI \-target
+.I name
+is accepted, but ignored.
+.TP
+.BI \-tempdir= dir
+Store temporary files in the directory
+.I dir/
+or
+.I
+dir\\.
+The default is usually
+.BR /tmp .
+.TP
+.BI \-W xarg
+pass argument
+.I arg
+to the program indicated by
+.IR x ;
+.I x
+can be one of
+.BR p ,
+.BR f ,
+.BR a ,
+or
+.BR l ,
+which refer, respectively, to the preprocessor, the compiler proper,
+the assembler, and the loader.
+.I arg
+is passed as given; if a
+.B \-
+is expected, it must be given explicitly.
+.BI \-Wo arg
+specifies a system-specific option,
+.IR arg .
+.PP
+Other arguments
+are taken to be either loader option arguments, or C-compatible
+object programs, typically produced by an earlier
+.I lcc
+run, or perhaps libraries of C-compatible routines.
+Duplicate object files are ignored.
+These programs, together with the results of any
+compilations specified, are loaded (in the order
+given) to produce an executable program with name
+.BR a.out
+(UNIX) or
+.BR a.exe
+(Windows).
+.PP
+.I lcc
+assigns the most frequently referenced scalar parameters and
+locals to registers whenever possible.
+For each block,
+explicit register declarations are obeyed first;
+remaining registers are assigned to automatic locals if they
+are `referenced' at least 3 times.
+Each top-level occurrence of an identifier
+counts as 1 reference. Occurrences in a loop,
+either of the then/else arms of an if statement, or a case
+in a switch statement each count, respectively, as 10, 1/2, or 1/10 references.
+These values are adjusted accordingly for nested control structures.
+.B \-Wf\-a
+causes
+.I lcc
+to read a
+.B prof.out
+file from a previous execution and to use the data therein
+to compute reference counts (see
+.BR \-b ).
+.PP
+.I lcc
+is a cross compiler;
+.BI \-Wf\-target= target/os
+causes
+.I lcc
+to generate code for
+.I target
+running the operating system denoted by
+.IR os .
+The supported
+.I target/os
+combinations may include
+.PP
+.RS
+.ta \w'sparc/solarisxx'u
+.nf
+alpha/osf ALPHA, OSF 3.2
+mips/irix big-endian MIPS, IRIX 5.2
+mips/ultrix little-endian MIPS, ULTRIX 4.3
+sparc/solaris SPARC, Solaris 2.3
+x86/win32 x86, Windows NT 4.0/Windows 95/98
+x86/linux x86, Linux
+symbolic text rendition of the generated code
+null no output
+.fi
+.RE
+.PP
+For
+.BR \-Wf\-target=symbolic ,
+the option
+.B \-Wf-html
+causes the text rendition to be emitted as HTML.
+.B
+.SH LIMITATIONS
+.PP
+.I lcc
+accepts the C programming language
+as described in the ANSI standard.
+If
+.I lcc
+is used with the GNU C preprocessor, the
+.B \-Wp\-trigraphs
+option is required to enable trigraph sequences.
+.PP
+Plain int bit fields are signed.
+Bit fields are aligned like unsigned integers but are otherwise laid out
+as by most standard C compilers.
+Some compilers, such as the GNU C compiler,
+may choose other, incompatible layouts.
+.PP
+Likewise, calling conventions are intended to be compatible with
+the host C compiler,
+except possibly for passing and returning structures.
+Specifically,
+.I lcc
+passes and returns structures like host ANSI C compilers
+on most targets, but some older host C compilers use different conventions.
+Consequently, calls to/from such functions compiled with
+older C compilers may not work.
+Calling a function that returns
+a structure without declaring it as such violates
+the ANSI standard and may cause a fault.
+.SH FILES
+.PP
+The file names listed below are
+.IR typical ,
+but vary among installations; installation-dependent variants
+can be displayed by running
+.I lcc
+with the
+.B \-v
+option.
+.PP
+.RS
+.ta \w'$LCCDIR/liblcc.{a,lib}XX'u
+.nf
+file.{c,C} input file
+file.{s,asm} assembly-language file
+file.{o,obj} object file
+a.{out,exe} loaded output
+/tmp/lcc* temporary files
+$LCCDIR/cpp preprocessor
+$LCCDIR/rcc compiler
+$LCCDIR/liblcc.{a,lib} \fIlcc\fP-specific library
+/lib/crt0.o runtime startup (UNIX)
+/lib/[gm]crt0.o startups for profiling (UNIX)
+/lib/libc.a standard library (UNIX)
+$LCCDIR/include ANSI standard headers
+/usr/local/include local headers
+/usr/include traditional headers
+prof.out file produced for \fIbprint\fR(1)
+mon.out file produced for \fIprof\fR(1)
+gmon.out file produced for \fIgprof\fR(1)
+.fi
+.RE
+.PP
+.I lcc
+predefines the macro
+.B __LCC__
+on all systems.
+It may also predefine some installation-dependent symbols; option
+.B \-v
+exposes them.
+.SH "SEE ALSO"
+.PP
+C. W. Fraser and D. R. Hanson,
+.I A Retargetable C Compiler: Design and Implementation,
+Addison-Wesley, 1995. ISBN 0-8053-1670-1.
+.PP
+The World-Wide Web page at http://www.cs.princeton.edu/software/lcc/.
+.PP
+S. P. Harbison and G. L. Steele, Jr.,
+.I C: A Reference Manual,
+4th ed., Prentice-Hall, 1995.
+.PP
+B. W. Kernighan and D. M. Ritchie,
+.I The C Programming Language,
+2nd ed., Prentice-Hall, 1988.
+.PP
+American National Standards Inst.,
+.I American National Standard for Information Systems\(emProgramming
+.IR Language\(emC ,
+ANSI X3.159-1989, New York, 1990.
+.br
+.SH BUGS
+Mail bug reports along with the shortest preprocessed program
+that exposes them and the details reported by
+.IR lcc 's
+.B \-v
+option to lcc-bugs@princeton.edu. The WWW page at
+URL http://www.cs.princeton.edu/software/lcc/
+includes detailed instructions for reporting bugs.
+.PP
+The ANSI standard headers conform to the specifications in
+the Standard, which may be too restrictive for some applications,
+but necessary for portability.
+Functions given in the ANSI headers may be missing from
+some local C libraries (e.g., wide-character functions)
+or may not correspond exactly to the local versions;
+for example, the ANSI standard
+stdio.h
+specifies that
+.IR printf ,
+.IR fprintf ,
+and
+.I sprintf
+return the number of characters written to the file or array,
+but some existing libraries don't implement this convention.
+.PP
+On the MIPS and SPARC, old-style variadic functions must use
+varargs.h
+from MIPS or Sun. New-style is recommended.
+.PP
+With
+.BR \-b ,
+files compiled
+.I without
+.B \-b
+may cause
+.I bprint
+to print erroneous call graphs.
+For example, if
+.B f
+calls
+.B g
+calls
+.B h
+and
+.B f
+and
+.B h
+are compiled with
+.BR \-b ,
+but
+.B g
+is not,
+.B bprint
+will report that
+.B f
+called
+.BR h .
+The total number of calls is correct, however.
diff --git a/src/tools/lcc/doc/lcc.pdf b/src/tools/lcc/doc/lcc.pdf
new file mode 100644
index 0000000..6134de6
--- /dev/null
+++ b/src/tools/lcc/doc/lcc.pdf
Binary files differ
diff --git a/src/tools/lcc/etc/bytecode.c b/src/tools/lcc/etc/bytecode.c
new file mode 100644
index 0000000..a5855de
--- /dev/null
+++ b/src/tools/lcc/etc/bytecode.c
@@ -0,0 +1,70 @@
+/* quake3 bytecode target */
+
+#include <string.h>
+#include <stdio.h>
+#include "../../../qcommon/q_platform.h"
+
+#ifdef _WIN32
+#define BINEXT ".exe"
+#else
+#define BINEXT ""
+#endif
+
+char *suffixes[] = { ".c", ".i", ".asm", ".o", ".out", 0 };
+char inputs[256] = "";
+char *cpp[] = { "q3cpp" BINEXT,
+ "-D__STDC__=1", "-D__STRICT_ANSI__", "-D__signed__=signed", "-DQ3_VM",
+ "$1", "$2", "$3", 0 };
+char *include[] = { 0 };
+char *com[] = { "q3rcc" BINEXT, "-target=bytecode", "$1", "$2", "$3", 0 };
+char *ld[] = { 0 };
+char *as[] = { 0 };
+
+extern char *concat(char *, char *);
+
+/*
+===============
+UpdatePaths
+
+Updates the paths to q3cpp and q3rcc based on
+the directory that contains q3lcc
+===============
+*/
+void UpdatePaths( const char *lccBinary )
+{
+ char basepath[ 1024 ];
+ char *p;
+
+ strncpy( basepath, lccBinary, 1024 );
+ p = strrchr( basepath, PATH_SEP );
+
+ if( p )
+ {
+ *( p + 1 ) = '\0';
+
+ cpp[ 0 ] = concat( basepath, "q3cpp" BINEXT );
+ com[ 0 ] = concat( basepath, "q3rcc" BINEXT );
+ }
+}
+
+int option(char *arg) {
+ if (strncmp(arg, "-lccdir=", 8) == 0) {
+ cpp[0] = concat(&arg[8], "/q3cpp" BINEXT);
+ include[0] = concat("-I", concat(&arg[8], "/include"));
+ com[0] = concat(&arg[8], "/q3rcc" BINEXT);
+ } else if (strncmp(arg, "-lcppdir=", 9) == 0) {
+ cpp[0] = concat(&arg[9], "/q3cpp" BINEXT);
+ } else if (strncmp(arg, "-lrccdir=", 9) == 0) {
+ com[0] = concat(&arg[9], "/q3rcc" BINEXT);
+ } else if (strcmp(arg, "-p") == 0 || strcmp(arg, "-pg") == 0) {
+ fprintf( stderr, "no profiling supported, %s ignored.\n", arg);
+ } else if (strcmp(arg, "-b") == 0)
+ ;
+ else if (strcmp(arg, "-g") == 0)
+ fprintf( stderr, "no debugging supported, %s ignored.\n", arg);
+ else if (strncmp(arg, "-ld=", 4) == 0 || strcmp(arg, "-static") == 0) {
+ fprintf( stderr, "no linking supported, %s ignored.\n", arg);
+ } else
+ return 0;
+ return 1;
+}
diff --git a/src/tools/lcc/etc/lcc.c b/src/tools/lcc/etc/lcc.c
new file mode 100644
index 0000000..aa3e789
--- /dev/null
+++ b/src/tools/lcc/etc/lcc.c
@@ -0,0 +1,857 @@
+/*
+ * lcc [ option ]... [ file | -llib ]...
+ * front end for the ANSI C compiler
+ */
+static char rcsid[] = "Id: dummy rcsid";
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <signal.h>
+#ifdef WIN32
+#include <process.h> /* getpid() */
+#include <io.h> /* access() */
+#else
+#include <unistd.h>
+#endif
+
+#ifndef TEMPDIR
+#define TEMPDIR "/tmp"
+#endif
+
+typedef struct list *List;
+struct list { /* circular list nodes: */
+ char *str; /* option or file name */
+ List link; /* next list element */
+};
+
+static void *alloc(int);
+static List append(char *,List);
+extern char *basename(char *);
+static int callsys(char *[]);
+extern char *concat(char *, char *);
+static int compile(char *, char *);
+static void compose(char *[], List, List, List);
+static void error(char *, char *);
+static char *exists(char *);
+static char *first(char *);
+static int filename(char *, char *);
+static List find(char *, List);
+static void help(void);
+static void initinputs(void);
+static void interrupt(int);
+static void opt(char *);
+static List path2list(const char *);
+extern int main(int, char *[]);
+extern char *replace(const char *, int, int);
+static void rm(List);
+extern char *strsave(const char *);
+extern char *stringf(const char *, ...);
+extern int suffix(char *, char *[], int);
+extern char *tempname(char *);
+
+#ifndef __sun
+extern int getpid(void);
+#endif
+
+extern char *cpp[], *include[], *com[], *as[],*ld[], inputs[], *suffixes[];
+extern int option(char *);
+
+static int errcnt; /* number of errors */
+static int Eflag; /* -E specified */
+static int Sflag = 1; /* -S specified */ //for Q3 we always generate asm
+static int cflag; /* -c specified */
+static int verbose; /* incremented for each -v */
+static List llist[2]; /* loader files, flags */
+static List alist; /* assembler flags */
+static List clist; /* compiler flags */
+static List plist; /* preprocessor flags */
+static List ilist; /* list of additional includes from LCCINPUTS */
+static List rmlist; /* list of files to remove */
+static char *outfile; /* ld output file or -[cS] object file */
+static int ac; /* argument count */
+static char **av; /* argument vector */
+char *tempdir = TEMPDIR; /* directory for temporary files */
+static char *progname;
+static List lccinputs; /* list of input directories */
+
+extern void UpdatePaths( const char *lccBinary );
+
+int main(int argc, char *argv[]) {
+ int i, j, nf;
+
+ progname = argv[0];
+
+ UpdatePaths( progname );
+
+ ac = argc + 50;
+ av = alloc(ac*sizeof(char *));
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+ signal(SIGINT, interrupt);
+ if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
+ signal(SIGTERM, interrupt);
+#ifdef SIGHUP
+ if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
+ signal(SIGHUP, interrupt);
+#endif
+ if (getenv("TMP"))
+ tempdir = getenv("TMP");
+ else if (getenv("TEMP"))
+ tempdir = getenv("TEMP");
+ else if (getenv("TMPDIR"))
+ tempdir = getenv("TMPDIR");
+ assert(tempdir);
+ i = strlen(tempdir);
+ for (; (i > 0 && tempdir[i-1] == '/') || tempdir[i-1] == '\\'; i--)
+ tempdir[i-1] = '\0';
+ if (argc <= 1) {
+ help();
+ exit(0);
+ }
+ plist = append("-D__LCC__", 0);
+ initinputs();
+ if (getenv("LCCDIR"))
+ option(stringf("-lccdir=%s", getenv("LCCDIR")));
+ for (nf = 0, i = j = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-o") == 0) {
+ if (++i < argc) {
+ if (suffix(argv[i], suffixes, 2) >= 0) {
+ error("-o would overwrite %s", argv[i]);
+ exit(8);
+ }
+ outfile = argv[i];
+ continue;
+ } else {
+ error("unrecognized option `%s'", argv[i-1]);
+ exit(8);
+ }
+ } else if (strcmp(argv[i], "-target") == 0) {
+ if (argv[i+1] && *argv[i+1] != '-')
+ i++;
+ continue;
+ } else if (*argv[i] == '-' && argv[i][1] != 'l') {
+ opt(argv[i]);
+ continue;
+ } else if (*argv[i] != '-' && suffix(argv[i], suffixes, 3) >= 0)
+ nf++;
+ argv[j++] = argv[i];
+ }
+ if ((cflag || Sflag) && outfile && nf != 1) {
+ fprintf(stderr, "%s: -o %s ignored\n", progname, outfile);
+ outfile = 0;
+ }
+ argv[j] = 0;
+ for (i = 0; include[i]; i++)
+ plist = append(include[i], plist);
+ if (ilist) {
+ List b = ilist;
+ do {
+ b = b->link;
+ plist = append(b->str, plist);
+ } while (b != ilist);
+ }
+ ilist = 0;
+ for (i = 1; argv[i]; i++)
+ if (*argv[i] == '-')
+ opt(argv[i]);
+ else {
+ char *name = exists(argv[i]);
+ if (name) {
+ if (strcmp(name, argv[i]) != 0
+ || (nf > 1 && suffix(name, suffixes, 3) >= 0))
+ fprintf(stderr, "%s:\n", name);
+ filename(name, 0);
+ } else
+ error("can't find `%s'", argv[i]);
+ }
+ if (errcnt == 0 && !Eflag && !Sflag && !cflag && llist[1]) {
+ compose(ld, llist[0], llist[1],
+ append(outfile ? outfile : concat("a", first(suffixes[4])), 0));
+ if (callsys(av))
+ errcnt++;
+ }
+ rm(rmlist);
+ return errcnt ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+/* alloc - allocate n bytes or die */
+static void *alloc(int n) {
+ static char *avail, *limit;
+
+ n = (n + sizeof(char *) - 1)&~(sizeof(char *) - 1);
+ if (n >= limit - avail) {
+ avail = malloc(n + 4*1024);
+ assert(avail);
+ limit = avail + n + 4*1024;
+ }
+ avail += n;
+ return avail - n;
+}
+
+/* append - append a node with string str onto list, return new list */
+static List append(char *str, List list) {
+ List p = alloc(sizeof *p);
+
+ p->str = str;
+ if (list) {
+ p->link = list->link;
+ list->link = p;
+ } else
+ p->link = p;
+ return p;
+}
+
+/* basename - return base name for name, e.g. /usr/drh/foo.c => foo */
+char *basename(char *name) {
+ char *s, *b, *t = 0;
+
+ for (b = s = name; *s; s++)
+ if (*s == '/' || *s == '\\') {
+ b = s + 1;
+ t = 0;
+ } else if (*s == '.')
+ t = s;
+ s = strsave(b);
+ if (t)
+ s[t-b] = 0;
+ return s;
+}
+
+#ifdef WIN32
+#include <process.h>
+
+static char *escapeDoubleQuotes(const char *string) {
+ int stringLength = strlen(string);
+ int bufferSize = stringLength + 1;
+ int i, j;
+ char *newString;
+
+ if (string == NULL)
+ return NULL;
+
+ for (i = 0; i < stringLength; i++) {
+ if (string[i] == '"')
+ bufferSize++;
+ }
+
+ newString = (char*)malloc(bufferSize);
+
+ if (newString == NULL)
+ return NULL;
+
+ for (i = 0, j = 0; i < stringLength; i++) {
+ if (string[i] == '"')
+ newString[j++] = '\\';
+
+ newString[j++] = string[i];
+ }
+
+ newString[j] = '\0';
+
+ return newString;
+}
+
+static int spawn(const char *cmdname, char **argv) {
+ int argc = 0;
+ char **newArgv = argv;
+ int i;
+ intptr_t exitStatus;
+
+ // _spawnvp removes double quotes from arguments, so we
+ // have to escape them manually
+ while (*newArgv++ != NULL)
+ argc++;
+
+ newArgv = (char **)malloc(sizeof(char*) * (argc + 1));
+
+ for (i = 0; i < argc; i++)
+ newArgv[i] = escapeDoubleQuotes(argv[i]);
+
+ newArgv[argc] = NULL;
+
+ exitStatus = _spawnvp(_P_WAIT, cmdname, (const char *const *)newArgv);
+
+ for (i = 0; i < argc; i++)
+ free(newArgv[i]);
+
+ free(newArgv);
+ return exitStatus;
+}
+
+#else
+
+#define _P_WAIT 0
+#ifndef __sun
+extern int fork(void);
+#endif
+extern int wait(int *);
+
+static int spawn(const char *cmdname, char **argv) {
+ int pid, n, status;
+
+ switch (pid = fork()) {
+ case -1:
+ fprintf(stderr, "%s: no more processes\n", progname);
+ return 100;
+ case 0:
+ // TTimo removing hardcoded paths, searching in $PATH
+ execvp(cmdname, argv);
+ fprintf(stderr, "%s: ", progname);
+ perror(cmdname);
+ fflush(stdout);
+ exit(100);
+ }
+ while ((n = wait(&status)) != pid && n != -1)
+ ;
+ if (n == -1)
+ status = -1;
+ if (status&0377) {
+ fprintf(stderr, "%s: fatal error in %s\n", progname, cmdname);
+ status |= 0400;
+ }
+ return (status>>8)&0377;
+}
+#endif
+
+/* callsys - execute the command described by av[0...], return status */
+static int callsys(char **av) {
+ int i, status = 0;
+ static char **argv;
+ static int argc;
+ char *executable;
+
+ for (i = 0; av[i] != NULL; i++)
+ ;
+ if (i + 1 > argc) {
+ argc = i + 1;
+ if (argv == NULL)
+ argv = malloc(argc*sizeof *argv);
+ else
+ argv = realloc(argv, argc*sizeof *argv);
+ assert(argv);
+ }
+ for (i = 0; status == 0 && av[i] != NULL; ) {
+ int j = 0;
+ char *s = NULL;
+ for ( ; av[i] != NULL && (s = strchr(av[i], '\n')) == NULL; i++)
+ argv[j++] = av[i];
+ if (s != NULL) {
+ if (s > av[i])
+ argv[j++] = stringf("%.*s", s - av[i], av[i]);
+ if (s[1] != '\0')
+ av[i] = s + 1;
+ else
+ i++;
+ }
+ argv[j] = NULL;
+ executable = strsave( argv[0] );
+ argv[0] = stringf( "\"%s\"", argv[0] );
+ if (verbose > 0) {
+ int k;
+ fprintf(stderr, "%s", argv[0]);
+ for (k = 1; argv[k] != NULL; k++)
+ fprintf(stderr, " %s", argv[k]);
+ fprintf(stderr, "\n");
+ }
+ if (verbose < 2)
+ status = spawn(executable, argv);
+ if (status == -1) {
+ fprintf(stderr, "%s: ", progname);
+ perror(argv[0]);
+ }
+ }
+ return status;
+}
+
+/* concat - return concatenation of strings s1 and s2 */
+char *concat(char *s1, char *s2) {
+ int n = strlen(s1);
+ char *s = alloc(n + strlen(s2) + 1);
+
+ strcpy(s, s1);
+ strcpy(s + n, s2);
+ return s;
+}
+
+/* compile - compile src into dst, return status */
+static int compile(char *src, char *dst) {
+ compose(com, clist, append(src, 0), append(dst, 0));
+ return callsys(av);
+}
+
+/* compose - compose cmd into av substituting a, b, c for $1, $2, $3, resp. */
+static void compose(char *cmd[], List a, List b, List c) {
+ int i, j;
+ List lists[3];
+
+ lists[0] = a;
+ lists[1] = b;
+ lists[2] = c;
+ for (i = j = 0; cmd[i]; i++) {
+ char *s = strchr(cmd[i], '$');
+ if (s && isdigit(s[1])) {
+ int k = s[1] - '0';
+ assert(k >=1 && k <= 3);
+ if ((b = lists[k-1])) {
+ b = b->link;
+ av[j] = alloc(strlen(cmd[i]) + strlen(b->str) - 1);
+ strncpy(av[j], cmd[i], s - cmd[i]);
+ av[j][s-cmd[i]] = '\0';
+ strcat(av[j], b->str);
+ strcat(av[j++], s + 2);
+ while (b != lists[k-1]) {
+ b = b->link;
+ assert(j < ac);
+ av[j++] = b->str;
+ };
+ }
+ } else if (*cmd[i]) {
+ assert(j < ac);
+ av[j++] = cmd[i];
+ }
+ }
+ av[j] = NULL;
+}
+
+/* error - issue error msg according to fmt, bump error count */
+static void error(char *fmt, char *msg) {
+ fprintf(stderr, "%s: ", progname);
+ fprintf(stderr, fmt, msg);
+ fprintf(stderr, "\n");
+ errcnt++;
+}
+
+/* exists - if `name' readable return its path name or return null */
+static char *exists(char *name) {
+ List b;
+
+ if ( (name[0] == '/' || name[0] == '\\' || name[2] == ':')
+ && access(name, 4) == 0)
+ return name;
+ if (!(name[0] == '/' || name[0] == '\\' || name[2] == ':')
+ && (b = lccinputs))
+ do {
+ b = b->link;
+ if (b->str[0]) {
+ char buf[1024];
+ sprintf(buf, "%s/%s", b->str, name);
+ if (access(buf, 4) == 0)
+ return strsave(buf);
+ } else if (access(name, 4) == 0)
+ return name;
+ } while (b != lccinputs);
+ if (verbose > 1)
+ return name;
+ return 0;
+}
+
+/* first - return first component in semicolon separated list */
+static char *first(char *list) {
+ char *s = strchr(list, ';');
+
+ if (s) {
+ char buf[1024];
+ strncpy(buf, list, s-list);
+ buf[s-list] = '\0';
+ return strsave(buf);
+ } else
+ return list;
+}
+
+/* filename - process file name argument `name', return status */
+static int filename(char *name, char *base) {
+ int status = 0;
+ static char *stemp, *itemp;
+
+ if (base == 0)
+ base = basename(name);
+ switch (suffix(name, suffixes, 4)) {
+ case 0: /* C source files */
+ compose(cpp, plist, append(name, 0), 0);
+ if (Eflag) {
+ status = callsys(av);
+ break;
+ }
+ if (itemp == NULL)
+ itemp = tempname(first(suffixes[1]));
+ compose(cpp, plist, append(name, 0), append(itemp, 0));
+ status = callsys(av);
+ if (status == 0)
+ return filename(itemp, base);
+ break;
+ case 1: /* preprocessed source files */
+ if (Eflag)
+ break;
+ if (Sflag)
+ status = compile(name, outfile ? outfile : concat(base, first(suffixes[2])));
+ else if ((status = compile(name, stemp?stemp:(stemp=tempname(first(suffixes[2]))))) == 0)
+ return filename(stemp, base);
+ break;
+ case 2: /* assembly language files */
+ if (Eflag)
+ break;
+ if (!Sflag) {
+ char *ofile;
+ if (cflag && outfile)
+ ofile = outfile;
+ else if (cflag)
+ ofile = concat(base, first(suffixes[3]));
+ else
+ ofile = tempname(first(suffixes[3]));
+ compose(as, alist, append(name, 0), append(ofile, 0));
+ status = callsys(av);
+ if (!find(ofile, llist[1]))
+ llist[1] = append(ofile, llist[1]);
+ }
+ break;
+ case 3: /* object files */
+ if (!find(name, llist[1]))
+ llist[1] = append(name, llist[1]);
+ break;
+ default:
+ if (Eflag) {
+ compose(cpp, plist, append(name, 0), 0);
+ status = callsys(av);
+ }
+ llist[1] = append(name, llist[1]);
+ break;
+ }
+ if (status)
+ errcnt++;
+ return status;
+}
+
+/* find - find 1st occurrence of str in list, return list node or 0 */
+static List find(char *str, List list) {
+ List b;
+
+ if ((b = list))
+ do {
+ if (strcmp(str, b->str) == 0)
+ return b;
+ } while ((b = b->link) != list);
+ return 0;
+}
+
+/* help - print help message */
+static void help(void) {
+ static char *msgs[] = {
+"", " [ option | file ]...\n",
+" except for -l, options are processed left-to-right before files\n",
+" unrecognized options are taken to be linker options\n",
+"-A warn about nonANSI usage; 2nd -A warns more\n",
+"-b emit expression-level profiling code; see bprint(1)\n",
+#ifdef sparc
+"-Bstatic -Bdynamic specify static or dynamic libraries\n",
+#endif
+"-Bdir/ use the compiler named `dir/rcc'\n",
+"-c compile only\n",
+"-dn set switch statement density to `n'\n",
+"-Dname -Dname=def define the preprocessor symbol `name'\n",
+"-E run only the preprocessor on the named C programs and unsuffixed files\n",
+"-g produce symbol table information for debuggers\n",
+"-help or -? print this message\n",
+"-Idir add `dir' to the beginning of the list of #include directories\n",
+"-lx search library `x'\n",
+"-N do not search the standard directories for #include files\n",
+"-n emit code to check for dereferencing zero pointers\n",
+"-O is ignored\n",
+"-o file leave the output in `file'\n",
+"-P print ANSI-style declarations for globals\n",
+"-p -pg emit profiling code; see prof(1) and gprof(1)\n",
+"-S compile to assembly language\n",
+#ifdef linux
+"-static specify static libraries (default is dynamic)\n",
+#endif
+"-t -tname emit function tracing calls to printf or to `name'\n",
+"-target name is ignored\n",
+"-tempdir=dir place temporary files in `dir/'", "\n"
+"-Uname undefine the preprocessor symbol `name'\n",
+"-v show commands as they are executed; 2nd -v suppresses execution\n",
+"-w suppress warnings\n",
+"-Woarg specify system-specific `arg'\n",
+"-W[pfal]arg pass `arg' to the preprocessor, compiler, assembler, or linker\n",
+ 0 };
+ int i;
+ char *s;
+
+ msgs[0] = progname;
+ for (i = 0; msgs[i]; i++) {
+ fprintf(stderr, "%s", msgs[i]);
+ if (strncmp("-tempdir", msgs[i], 8) == 0 && tempdir)
+ fprintf(stderr, "; default=%s", tempdir);
+ }
+#define xx(v) if ((s = getenv(#v))) fprintf(stderr, #v "=%s\n", s)
+ xx(LCCINPUTS);
+ xx(LCCDIR);
+#undef xx
+}
+
+/* initinputs - if LCCINPUTS or include is defined, use them to initialize various lists */
+static void initinputs(void) {
+ char *s = getenv("LCCINPUTS");
+ List b;
+
+ if (s == 0 || (s = inputs)[0] == 0)
+ s = ".";
+ if (s) {
+ lccinputs = path2list(s);
+ if ((b = lccinputs))
+ do {
+ b = b->link;
+ if (strcmp(b->str, ".") != 0) {
+ ilist = append(concat("-I", b->str), ilist);
+ if (strstr(com[1], "win32") == NULL)
+ llist[0] = append(concat("-L", b->str), llist[0]);
+ } else
+ b->str = "";
+ } while (b != lccinputs);
+ }
+}
+
+/* interrupt - catch interrupt signals */
+static void interrupt(int n) {
+ rm(rmlist);
+ exit(n = 100);
+}
+
+/* opt - process option in arg */
+static void opt(char *arg) {
+ switch (arg[1]) { /* multi-character options */
+ case 'W': /* -Wxarg */
+ if (arg[2] && arg[3])
+ switch (arg[2]) {
+ case 'o':
+ if (option(&arg[3]))
+ return;
+ break;
+ case 'p':
+ plist = append(&arg[3], plist);
+ return;
+ case 'f':
+ if (strcmp(&arg[3], "-C") || option("-b")) {
+ clist = append(&arg[3], clist);
+ return;
+ }
+ break; /* and fall thru */
+ case 'a':
+ alist = append(&arg[3], alist);
+ return;
+ case 'l':
+ llist[0] = append(&arg[3], llist[0]);
+ return;
+ }
+ fprintf(stderr, "%s: %s ignored\n", progname, arg);
+ return;
+ case 'd': /* -dn */
+ arg[1] = 's';
+ clist = append(arg, clist);
+ return;
+ case 't': /* -t -tname -tempdir=dir */
+ if (strncmp(arg, "-tempdir=", 9) == 0)
+ tempdir = arg + 9;
+ else
+ clist = append(arg, clist);
+ return;
+ case 'p': /* -p -pg */
+ if (option(arg))
+ clist = append(arg, clist);
+ else
+ fprintf(stderr, "%s: %s ignored\n", progname, arg);
+ return;
+ case 'D': /* -Dname -Dname=def */
+ case 'U': /* -Uname */
+ case 'I': /* -Idir */
+ plist = append(arg, plist);
+ return;
+ case 'B': /* -Bdir -Bstatic -Bdynamic */
+#ifdef sparc
+ if (strcmp(arg, "-Bstatic") == 0 || strcmp(arg, "-Bdynamic") == 0)
+ llist[1] = append(arg, llist[1]);
+ else
+#endif
+ {
+ static char *path;
+ if (path)
+ error("-B overwrites earlier option", 0);
+ path = arg + 2;
+ if (strstr(com[1], "win32") != NULL)
+ com[0] = concat(replace(path, '/', '\\'), concat("rcc", first(suffixes[4])));
+ else
+ com[0] = concat(path, "rcc");
+ if (path[0] == 0)
+ error("missing directory in -B option", 0);
+ }
+ return;
+ case 'h':
+ if (strcmp(arg, "-help") == 0) {
+ static int printed = 0;
+ case '?':
+ if (!printed)
+ help();
+ printed = 1;
+ return;
+ }
+#ifdef linux
+ case 's':
+ if (strcmp(arg,"-static") == 0) {
+ if (!option(arg))
+ fprintf(stderr, "%s: %s ignored\n", progname, arg);
+ return;
+ }
+#endif
+ }
+ if (arg[2] == 0)
+ switch (arg[1]) { /* single-character options */
+ case 'S':
+ Sflag++;
+ return;
+ case 'O':
+ fprintf(stderr, "%s: %s ignored\n", progname, arg);
+ return;
+ case 'A': case 'n': case 'w': case 'P':
+ clist = append(arg, clist);
+ return;
+ case 'g': case 'b':
+ if (option(arg))
+ clist = append(arg[1] == 'g' ? "-g2" : arg, clist);
+ else
+ fprintf(stderr, "%s: %s ignored\n", progname, arg);
+ return;
+ case 'G':
+ if (option(arg)) {
+ clist = append("-g3", clist);
+ llist[0] = append("-N", llist[0]);
+ } else
+ fprintf(stderr, "%s: %s ignored\n", progname, arg);
+ return;
+ case 'E':
+ Eflag++;
+ return;
+ case 'c':
+ cflag++;
+ return;
+ case 'N':
+ if (strcmp(basename(cpp[0]), "gcc-cpp") == 0)
+ plist = append("-nostdinc", plist);
+ include[0] = 0;
+ ilist = 0;
+ return;
+ case 'v':
+ if (verbose++ == 0) {
+ if (strcmp(basename(cpp[0]), "gcc-cpp") == 0)
+ plist = append(arg, plist);
+ clist = append(arg, clist);
+ fprintf(stderr, "%s %s\n", progname, rcsid);
+ }
+ return;
+ }
+ if (cflag || Sflag || Eflag)
+ fprintf(stderr, "%s: %s ignored\n", progname, arg);
+ else
+ llist[1] = append(arg, llist[1]);
+}
+
+/* path2list - convert a colon- or semicolon-separated list to a list */
+static List path2list(const char *path) {
+ List list = NULL;
+ char sep = ':';
+
+ if (path == NULL)
+ return NULL;
+ if (strchr(path, ';'))
+ sep = ';';
+ while (*path) {
+ char *p, buf[512];
+ if ((p = strchr(path, sep))) {
+ assert(p - path < sizeof buf);
+ strncpy(buf, path, p - path);
+ buf[p-path] = '\0';
+ } else {
+ assert(strlen(path) < sizeof buf);
+ strcpy(buf, path);
+ }
+ if (!find(buf, list))
+ list = append(strsave(buf), list);
+ if (p == 0)
+ break;
+ path = p + 1;
+ }
+ return list;
+}
+
+/* replace - copy str, then replace occurrences of from with to, return the copy */
+char *replace(const char *str, int from, int to) {
+ char *s = strsave(str), *p = s;
+
+ for ( ; (p = strchr(p, from)) != NULL; p++)
+ *p = to;
+ return s;
+}
+
+/* rm - remove files in list */
+static void rm(List list) {
+ if (list) {
+ List b = list;
+ if (verbose)
+ fprintf(stderr, "rm");
+ do {
+ if (verbose)
+ fprintf(stderr, " %s", b->str);
+ if (verbose < 2)
+ remove(b->str);
+ } while ((b = b->link) != list);
+ if (verbose)
+ fprintf(stderr, "\n");
+ }
+}
+
+/* strsave - return a saved copy of string str */
+char *strsave(const char *str) {
+ return strcpy(alloc(strlen(str)+1), str);
+}
+
+/* stringf - format and return a string */
+char *stringf(const char *fmt, ...) {
+ char buf[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ va_end(ap);
+ return strsave(buf);
+}
+
+/* suffix - if one of tails[0..n-1] holds a proper suffix of name, return its index */
+int suffix(char *name, char *tails[], int n) {
+ int i, len = strlen(name);
+
+ for (i = 0; i < n; i++) {
+ char *s = tails[i], *t;
+ for ( ; (t = strchr(s, ';')); s = t + 1) {
+ int m = t - s;
+ if (len > m && strncmp(&name[len-m], s, m) == 0)
+ return i;
+ }
+ if (*s) {
+ int m = strlen(s);
+ if (len > m && strncmp(&name[len-m], s, m) == 0)
+ return i;
+ }
+ }
+ return -1;
+}
+
+/* tempname - generate a temporary file name in tempdir with given suffix */
+char *tempname(char *suffix) {
+ static int n;
+ char *name = stringf("%s/lcc%d%d%s", tempdir, getpid(), n++, suffix);
+
+ if (strstr(com[1], "win32") != NULL)
+ name = replace(name, '/', '\\');
+ rmlist = append(name, rmlist);
+ return name;
+}
diff --git a/src/tools/lcc/lburg/gram.c b/src/tools/lcc/lburg/gram.c
new file mode 100644
index 0000000..c09f897
--- /dev/null
+++ b/src/tools/lcc/lburg/gram.c
@@ -0,0 +1,677 @@
+#if defined(__STDC__) || defined(__cplusplus)
+#define YYCONST const
+#define YYPARAMS(x) x
+#define YYDEFUN(name, arglist, args) name(args)
+#define YYAND ,
+#define YYPTR void *
+#else
+#define YYCONST
+#define YYPARAMS(x) ()
+#define YYDEFUN(name, arglist, args) name arglist args;
+#define YYAND ;
+#define YYPTR char *
+#endif
+#define YYBYACC 1
+#ifndef YYDONT_INCLUDE_STDIO
+#include <stdio.h>
+#endif
+#include <stdlib.h> /* for malloc/realloc/free */
+#line 2 "lburg/gram.y"
+#include <stdio.h>
+#include "lburg.h"
+/*lint -e616 -e527 -e652 -esym(552,yynerrs) -esym(563,yynewstate,yyerrlab) */
+static int yylineno = 0;
+#line 8 "lburg/gram.y"
+typedef union {
+ int n;
+ char *string;
+ Tree tree;
+} YYSTYPE;
+#line 37 "y.tab.c"
+#define TERMINAL 257
+#define START 258
+#define PPERCENT 259
+#define ID 260
+#define TEMPLATE 261
+#define CODE 262
+#define INT 263
+#define YYERRCODE 256
+static YYCONST short yylhs[] = { -1,
+ 0, 0, 4, 4, 6, 6, 6, 6, 7, 7,
+ 5, 5, 5, 5, 1, 3, 3, 3, 2,
+};
+static YYCONST short yylen[] = { 2,
+ 3, 1, 0, 2, 3, 3, 1, 2, 0, 4,
+ 0, 7, 2, 3, 1, 1, 4, 6, 1,
+};
+static YYCONST short yydefred[] = { 3,
+ 0, 0, 0, 9, 0, 11, 7, 4, 8, 0,
+ 15, 0, 0, 0, 5, 6, 0, 13, 0, 0,
+ 14, 0, 10, 0, 0, 0, 0, 0, 19, 0,
+ 17, 0, 12, 0, 18,
+};
+static YYCONST short yydgoto[] = { 1,
+ 12, 30, 25, 2, 13, 8, 10,
+};
+static YYCONST short yysindex[] = { 0,
+ 0, -4, -2, 0, -250, 0, 0, 0, 0, -9,
+ 0, 1, -10, -49, 0, 0, 3, 0, -44, -248,
+ 0, -244, 0, -22, -242, -244, -245, -37, 0, 10,
+ 0, -244, 0, -20, 0,
+};
+static YYCONST short yyrindex[] = { 0,
+ 0, 22, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 23, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, -39, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+};
+static YYCONST short yygindex[] = { 0,
+ 11, 0, -23, 0, 0, 0, 0,
+};
+#define YYTABLESIZE 255
+static YYCONST short yytable[] = { 18,
+ 15, 16, 28, 31, 16, 7, 32, 9, 34, 11,
+ 16, 20, 21, 22, 23, 24, 29, 26, 27, 33,
+ 35, 2, 1, 19, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 16, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 17, 0, 0, 0, 11,
+ 14, 3, 4, 5, 6,
+};
+static YYCONST short yycheck[] = { 10,
+ 10, 41, 26, 41, 44, 10, 44, 10, 32, 260,
+ 10, 61, 10, 58, 263, 260, 262, 40, 261, 10,
+ 41, 0, 0, 13, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 261, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 256, -1, -1, -1, 260,
+ 260, 256, 257, 258, 259,
+};
+#define YYFINAL 1
+#ifndef YYDEBUG
+#define YYDEBUG 0
+#endif
+#define YYMAXTOKEN 263
+#if YYDEBUG
+static YYCONST char *YYCONST yyname[] = {
+"end-of-file",0,0,0,0,0,0,0,0,0,"'\\n'",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,"'('","')'",0,0,"','",0,0,0,0,0,0,0,0,0,0,0,0,0,"':'",0,0,
+"'='",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+"TERMINAL","START","PPERCENT","ID","TEMPLATE","CODE","INT",
+};
+static YYCONST char *YYCONST yyrule[] = {
+"$accept : spec",
+"spec : decls PPERCENT rules",
+"spec : decls",
+"decls :",
+"decls : decls decl",
+"decl : TERMINAL blist '\\n'",
+"decl : START nonterm '\\n'",
+"decl : '\\n'",
+"decl : error '\\n'",
+"blist :",
+"blist : blist ID '=' INT",
+"rules :",
+"rules : rules nonterm ':' tree TEMPLATE cost '\\n'",
+"rules : rules '\\n'",
+"rules : rules error '\\n'",
+"nonterm : ID",
+"tree : ID",
+"tree : ID '(' tree ')'",
+"tree : ID '(' tree ',' tree ')'",
+"cost : CODE",
+};
+#endif
+#define YYLEX yylex()
+#define YYEMPTY -1
+#define yyclearin (yychar=(YYEMPTY))
+#define yyerrok (yyerrflag=0)
+#ifndef YYINITDEPTH
+#define YYINITDEPTH 200
+#endif
+#ifdef YYSTACKSIZE
+#ifndef YYMAXDEPTH
+#define YYMAXDEPTH YYSTACKSIZE
+#endif
+#else
+#ifdef YYMAXDEPTH
+#define YYSTACKSIZE YYMAXDEPTH
+#else
+#define YYSTACKSIZE 500
+#define YYMAXDEPTH 500
+#endif
+#endif
+#ifndef YYMAXSTACKSIZE
+#define YYMAXSTACKSIZE 10000
+#endif
+int yydebug;
+int yynerrs;
+int yyerrflag;
+int yychar;
+YYSTYPE yyval;
+YYSTYPE yylval;
+static short *yyss;
+static YYSTYPE *yyvs;
+static int yystacksize;
+#define yyfree(x) free(x)
+extern int yylex();
+
+static YYPTR
+YYDEFUN (yymalloc, (bytes), unsigned bytes)
+{
+ YYPTR ptr = (YYPTR) malloc (bytes);
+ if (ptr != 0) return (ptr);
+ yyerror ("yyparse: memory exhausted");
+ return (0);
+}
+
+static YYPTR
+YYDEFUN (yyrealloc, (old, bytes), YYPTR old YYAND unsigned bytes)
+{
+ YYPTR ptr = (YYPTR) realloc (old, bytes);
+ if (ptr != 0) return (ptr);
+ yyerror ("yyparse: memory exhausted");
+ return (0);
+}
+
+static int
+#ifdef __GNUC__
+inline
+#endif
+yygrow ()
+{
+#if YYDEBUG
+ int old_stacksize = yystacksize;
+#endif
+ short *new_yyss;
+ YYSTYPE *new_yyvs;
+
+ if (yystacksize == YYMAXSTACKSIZE)
+ return (1);
+ yystacksize += (yystacksize + 1 ) / 2;
+ if (yystacksize > YYMAXSTACKSIZE)
+ yystacksize = YYMAXSTACKSIZE;
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: growing stack size from %d to %d\n",
+ old_stacksize, yystacksize);
+#endif
+ new_yyss = (short *) yyrealloc ((char *)yyss, yystacksize * sizeof (short));
+ if (new_yyss == 0)
+ return (1);
+ new_yyvs = (YYSTYPE *) yyrealloc ((char *)yyvs, yystacksize * sizeof (YYSTYPE));
+ if (new_yyvs == 0)
+ {
+ yyfree (new_yyss);
+ return (1);
+ }
+ yyss = new_yyss;
+ yyvs = new_yyvs;
+ return (0);
+}
+#line 60 "lburg/gram.y"
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+int errcnt = 0;
+FILE *infp = NULL;
+FILE *outfp = NULL;
+static char buf[BUFSIZ], *bp = buf;
+static int ppercent = 0;
+static int code = 0;
+
+static int get(void) {
+ if (*bp == 0) {
+ bp = buf;
+ *bp = 0;
+ if (fgets(buf, sizeof buf, infp) == NULL)
+ return EOF;
+ yylineno++;
+ while (buf[0] == '%' && buf[1] == '{' && buf[2] == '\n') {
+ for (;;) {
+ if (fgets(buf, sizeof buf, infp) == NULL) {
+ yywarn("unterminated %{...%}\n");
+ return EOF;
+ }
+ yylineno++;
+ if (strcmp(buf, "%}\n") == 0)
+ break;
+ fputs(buf, outfp);
+ }
+ if (fgets(buf, sizeof buf, infp) == NULL)
+ return EOF;
+ yylineno++;
+ }
+ }
+ return *bp++;
+}
+
+void yyerror(char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (yylineno > 0)
+ fprintf(stderr, "line %d: ", yylineno);
+ vfprintf(stderr, fmt, ap);
+ if (fmt[strlen(fmt)-1] != '\n')
+ fprintf(stderr, "\n");
+ errcnt++;
+ va_end(ap);
+}
+
+int yylex(void) {
+ int c;
+
+ if (code) {
+ char *p;
+ bp += strspn(bp, " \t\f");
+ p = strchr(bp, '\n');
+ if (p == NULL)
+ p = strchr(bp, '\n');
+ while (p > bp && isspace(p[-1]))
+ p--;
+ yylval.string = alloc(p - bp + 1);
+ strncpy(yylval.string, bp, p - bp);
+ yylval.string[p - bp] = 0;
+ bp = p;
+ code--;
+ return CODE;
+ }
+ while ((c = get()) != EOF) {
+ switch (c) {
+ case ' ': case '\f': case '\t':
+ continue;
+ case '\n':
+ case '(': case ')': case ',':
+ case ':': case '=':
+ return c;
+ }
+ if (c == '%' && *bp == '%') {
+ bp++;
+ return ppercent++ ? 0 : PPERCENT;
+ } else if (c == '%' && strncmp(bp, "term", 4) == 0
+ && isspace(bp[4])) {
+ bp += 4;
+ return TERMINAL;
+ } else if (c == '%' && strncmp(bp, "start", 5) == 0
+ && isspace(bp[5])) {
+ bp += 5;
+ return START;
+ } else if (c == '"') {
+ char *p = strchr(bp, '"');
+ if (p == NULL) {
+ yyerror("missing \" in assembler template\n");
+ p = strchr(bp, '\n');
+ if (p == NULL)
+ p = strchr(bp, '\0');
+ }
+ assert(p);
+ yylval.string = alloc(p - bp + 1);
+ strncpy(yylval.string, bp, p - bp);
+ yylval.string[p - bp] = 0;
+ bp = *p == '"' ? p + 1 : p;
+ code++;
+ return TEMPLATE;
+ } else if (isdigit(c)) {
+ int n = 0;
+ do {
+ int d = c - '0';
+ if (n > (INT_MAX - d)/10)
+ yyerror("integer greater than %d\n", INT_MAX);
+ else
+ n = 10*n + d;
+ c = get();
+ } while (c != EOF && isdigit(c));
+ bp--;
+ yylval.n = n;
+ return INT;
+ } else if (isalpha(c)) {
+ char *p = bp - 1;
+ while (isalpha(*bp) || isdigit(*bp) || *bp == '_')
+ bp++;
+ yylval.string = alloc(bp - p + 1);
+ strncpy(yylval.string, p, bp - p);
+ yylval.string[bp - p] = 0;
+ return ID;
+ } else if (isprint(c))
+ yyerror("invalid character `%c'\n", c);
+ else
+ yyerror("invalid character `\\%03o'\n", (unsigned char)c);
+ }
+ return 0;
+}
+
+void yywarn(char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (yylineno > 0)
+ fprintf(stderr, "line %d: ", yylineno);
+ fprintf(stderr, "warning: ");
+ vfprintf(stderr, fmt, ap);
+}
+#line 403 "y.tab.c"
+#define YYABORT goto yyabort
+#define YYACCEPT goto yyaccept
+#define YYERROR goto yyerrlab
+
+#if YYDEBUG
+#ifdef __cplusplus
+extern "C" char *getenv();
+#else
+extern char *getenv();
+#endif
+#endif
+
+int
+yyparse()
+{
+ register int yym, yyn, yystate;
+ register YYSTYPE *yyvsp;
+ register short *yyssp;
+ short *yysse;
+#if YYDEBUG
+ register YYCONST char *yys;
+
+ if (yys = getenv("YYDEBUG"))
+ {
+ yyn = *yys;
+ if (yyn >= '0' && yyn <= '9')
+ yydebug = yyn - '0';
+ }
+#endif
+
+ yynerrs = 0;
+ yyerrflag = 0;
+ yychar = (-1);
+
+ if (yyss == 0)
+ {
+ yyss = (short *) yymalloc (YYSTACKSIZE * sizeof (short));
+ if (yyss == 0)
+ goto yyabort;
+ yyvs = (YYSTYPE *) yymalloc (YYSTACKSIZE * sizeof (YYSTYPE));
+ if (yyvs == 0)
+ {
+ yyfree (yyss);
+ goto yyabort;
+ }
+ yystacksize = YYSTACKSIZE;
+ }
+ yysse = yyss + yystacksize - 1;
+ yyssp = yyss;
+ yyvsp = yyvs;
+ *yyssp = yystate = 0;
+ goto yyloop;
+
+yypush_lex:
+ yyval = yylval;
+ yystate = yytable[yyn];
+yypush:
+ if (yyssp >= yysse)
+ {
+ int depth = yyssp - yyss;
+ if (yygrow() != 0)
+ goto yyoverflow;
+ yysse = yyss + yystacksize -1;
+ yyssp = depth + yyss;
+ yyvsp = depth + yyvs;
+ }
+ *++yyssp = yystate;
+ *++yyvsp = yyval;
+
+yyloop:
+ if ((yyn = yydefred[yystate])) goto yyreduce;
+ yyn = yysindex[yystate];
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, reading %d (%s)\n", yystate,
+ yychar, yys);
+ }
+#endif
+ }
+ if (yyn != 0
+ && ((yyn += yychar), ((unsigned)yyn <= (unsigned)YYTABLESIZE))
+ && yycheck[yyn] == yychar)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, shifting to state %d\n",
+ yystate, yytable[yyn]);
+#endif
+ if (yyerrflag > 0) --yyerrflag;
+ yychar = (-1);
+ goto yypush_lex;
+ }
+ yyn = yyrindex[yystate];
+ if (yyn != 0
+ && ((yyn += yychar), ((unsigned)yyn <= (unsigned)YYTABLESIZE))
+ && yycheck[yyn] == yychar)
+ {
+ yyn = yytable[yyn];
+ goto yyreduce;
+ }
+ if (yyerrflag) goto yyinrecovery;
+#ifdef lint
+ goto yynewerror;
+yynewerror:
+#endif
+ yyerror("syntax error");
+#ifdef lint
+ goto yyerrlab;
+yyerrlab:
+#endif
+ ++yynerrs;
+yyinrecovery:
+ if (yyerrflag < 3)
+ {
+ yyerrflag = 3;
+ for (;;)
+ {
+ yyn = yysindex[*yyssp];
+ if (yyn != 0
+ && ((yyn += YYERRCODE), ((unsigned)yyn <= (unsigned)YYTABLESIZE))
+ && yycheck[yyn] == YYERRCODE)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, error recovery shifting\
+ to state %d\n", *yyssp, yytable[yyn]);
+#endif
+ goto yypush_lex;
+ }
+ else
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: error recovery discarding state %d\n",
+ *yyssp);
+#endif
+ if (yyssp <= yyss) goto yyabort;
+ --yyssp;
+ --yyvsp;
+ }
+ }
+ }
+ else
+ {
+ if (yychar == 0) goto yyabort;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, error recovery discards token %d (%s)\n",
+ yystate, yychar, yys);
+ }
+#endif
+ yychar = (-1);
+ goto yyloop;
+ }
+yyreduce:
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, reducing by rule %d (%s)\n",
+ yystate, yyn, yyrule[yyn]);
+#endif
+ yym = yylen[yyn];
+ yyval = yyvsp[1-yym];
+ switch (yyn)
+ {
+case 1:
+#line 22 "lburg/gram.y"
+{ yylineno = 0; }
+break;
+case 2:
+#line 23 "lburg/gram.y"
+{ yylineno = 0; }
+break;
+case 6:
+#line 31 "lburg/gram.y"
+{
+ if (nonterm(yyvsp[-1].string)->number != 1)
+ yyerror("redeclaration of the start symbol\n");
+ }
+break;
+case 8:
+#line 36 "lburg/gram.y"
+{ yyerrok; }
+break;
+case 10:
+#line 40 "lburg/gram.y"
+{ term(yyvsp[-2].string, yyvsp[0].n); }
+break;
+case 12:
+#line 44 "lburg/gram.y"
+{ rule(yyvsp[-5].string, yyvsp[-3].tree, yyvsp[-2].string, yyvsp[-1].string); }
+break;
+case 14:
+#line 46 "lburg/gram.y"
+{ yyerrok; }
+break;
+case 15:
+#line 49 "lburg/gram.y"
+{ nonterm(yyval.string = yyvsp[0].string); }
+break;
+case 16:
+#line 52 "lburg/gram.y"
+{ yyval.tree = tree(yyvsp[0].string, 0, 0); }
+break;
+case 17:
+#line 53 "lburg/gram.y"
+{ yyval.tree = tree(yyvsp[-3].string, yyvsp[-1].tree, 0); }
+break;
+case 18:
+#line 54 "lburg/gram.y"
+{ yyval.tree = tree(yyvsp[-5].string, yyvsp[-3].tree, yyvsp[-1].tree); }
+break;
+case 19:
+#line 57 "lburg/gram.y"
+{ if (*yyvsp[0].string == 0) yyval.string = "0"; }
+break;
+#line 630 "y.tab.c"
+ }
+ yyssp -= yym;
+ yyvsp -= yym;
+ yym = yylhs[yyn];
+ yystate = *yyssp;
+ if (yystate == 0 && yym == 0)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: after reduction, shifting from state 0 to\
+ state %d\n", YYFINAL);
+#endif
+ yystate = YYFINAL;
+ *++yyssp = YYFINAL;
+ *++yyvsp = yyval;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, reading %d (%s)\n",
+ YYFINAL, yychar, yys);
+ }
+#endif
+ }
+ if (yychar == 0) goto yyaccept;
+ goto yyloop;
+ }
+ yyn = yygindex[yym];
+ if (yyn != 0
+ && ((yyn += yystate), ((unsigned)yyn <= (unsigned)YYTABLESIZE))
+ && yycheck[yyn] == yystate)
+ yystate = yytable[yyn];
+ else
+ yystate = yydgoto[yym];
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: after reduction, shifting from state %d \
+to state %d\n", *yyssp, yystate);
+#endif
+ goto yypush;
+yyoverflow:
+ yyerror("yacc stack overflow");
+yyabort:
+ return (1);
+yyaccept:
+ return (0);
+}
diff --git a/src/tools/lcc/lburg/gram.y b/src/tools/lcc/lburg/gram.y
new file mode 100644
index 0000000..1ecd8a9
--- /dev/null
+++ b/src/tools/lcc/lburg/gram.y
@@ -0,0 +1,202 @@
+%{
+#include <stdio.h>
+#include "lburg.h"
+static char rcsid[] = "$Id: gram.y 145 2001-10-17 21:53:10Z timo $";
+/*lint -e616 -e527 -e652 -esym(552,yynerrs) -esym(563,yynewstate,yyerrlab) */
+static int yylineno = 0;
+%}
+%union {
+ int n;
+ char *string;
+ Tree tree;
+}
+%term TERMINAL
+%term START
+%term PPERCENT
+
+%token <string> ID TEMPLATE CODE
+%token <n> INT
+%type <string> nonterm cost
+%type <tree> tree
+%%
+spec : decls PPERCENT rules { yylineno = 0; }
+ | decls { yylineno = 0; }
+ ;
+
+decls : /* lambda */
+ | decls decl
+ ;
+
+decl : TERMINAL blist '\n'
+ | START nonterm '\n' {
+ if (nonterm($2)->number != 1)
+ yyerror("redeclaration of the start symbol\n");
+ }
+ | '\n'
+ | error '\n' { yyerrok; }
+ ;
+
+blist : /* lambda */
+ | blist ID '=' INT { term($2, $4); }
+ ;
+
+rules : /* lambda */
+ | rules nonterm ':' tree TEMPLATE cost '\n' { rule($2, $4, $5, $6); }
+ | rules '\n'
+ | rules error '\n' { yyerrok; }
+ ;
+
+nonterm : ID { nonterm($$ = $1); }
+ ;
+
+tree : ID { $$ = tree($1, 0, 0); }
+ | ID '(' tree ')' { $$ = tree($1, $3, 0); }
+ | ID '(' tree ',' tree ')' { $$ = tree($1, $3, $5); }
+ ;
+
+cost : CODE { if (*$1 == 0) $$ = "0"; }
+ ;
+%%
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+int errcnt = 0;
+FILE *infp = NULL;
+FILE *outfp = NULL;
+static char buf[BUFSIZ], *bp = buf;
+static int ppercent = 0;
+static int code = 0;
+
+static int get(void) {
+ if (*bp == 0) {
+ bp = buf;
+ *bp = 0;
+ if (fgets(buf, sizeof buf, infp) == NULL)
+ return EOF;
+ yylineno++;
+ while (buf[0] == '%' && buf[1] == '{' && buf[2] == '\n') {
+ for (;;) {
+ if (fgets(buf, sizeof buf, infp) == NULL) {
+ yywarn("unterminated %{...%}\n");
+ return EOF;
+ }
+ yylineno++;
+ if (strcmp(buf, "%}\n") == 0)
+ break;
+ fputs(buf, outfp);
+ }
+ if (fgets(buf, sizeof buf, infp) == NULL)
+ return EOF;
+ yylineno++;
+ }
+ }
+ return *bp++;
+}
+
+void yyerror(char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (yylineno > 0)
+ fprintf(stderr, "line %d: ", yylineno);
+ vfprintf(stderr, fmt, ap);
+ if (fmt[strlen(fmt)-1] != '\n')
+ fprintf(stderr, "\n");
+ errcnt++;
+ va_end(ap);
+}
+
+int yylex(void) {
+ int c;
+
+ if (code) {
+ char *p;
+ bp += strspn(bp, " \t\f");
+ p = strchr(bp, '\n');
+ if (p == NULL)
+ p = strchr(bp, '\n');
+ while (p > bp && isspace(p[-1]))
+ p--;
+ yylval.string = alloc(p - bp + 1);
+ strncpy(yylval.string, bp, p - bp);
+ yylval.string[p - bp] = 0;
+ bp = p;
+ code--;
+ return CODE;
+ }
+ while ((c = get()) != EOF) {
+ switch (c) {
+ case ' ': case '\f': case '\t':
+ continue;
+ case '\n':
+ case '(': case ')': case ',':
+ case ':': case '=':
+ return c;
+ }
+ if (c == '%' && *bp == '%') {
+ bp++;
+ return ppercent++ ? 0 : PPERCENT;
+ } else if (c == '%' && strncmp(bp, "term", 4) == 0
+ && isspace(bp[4])) {
+ bp += 4;
+ return TERMINAL;
+ } else if (c == '%' && strncmp(bp, "start", 5) == 0
+ && isspace(bp[5])) {
+ bp += 5;
+ return START;
+ } else if (c == '"') {
+ char *p = strchr(bp, '"');
+ if (p == NULL) {
+ yyerror("missing \" in assembler template\n");
+ p = strchr(bp, '\n');
+ if (p == NULL)
+ p = strchr(bp, '\0');
+ }
+ assert(p);
+ yylval.string = alloc(p - bp + 1);
+ strncpy(yylval.string, bp, p - bp);
+ yylval.string[p - bp] = 0;
+ bp = *p == '"' ? p + 1 : p;
+ code++;
+ return TEMPLATE;
+ } else if (isdigit(c)) {
+ int n = 0;
+ do {
+ int d = c - '0';
+ if (n > (INT_MAX - d)/10)
+ yyerror("integer greater than %d\n", INT_MAX);
+ else
+ n = 10*n + d;
+ c = get();
+ } while (c != EOF && isdigit(c));
+ bp--;
+ yylval.n = n;
+ return INT;
+ } else if (isalpha(c)) {
+ char *p = bp - 1;
+ while (isalpha(*bp) || isdigit(*bp) || *bp == '_')
+ bp++;
+ yylval.string = alloc(bp - p + 1);
+ strncpy(yylval.string, p, bp - p);
+ yylval.string[bp - p] = 0;
+ return ID;
+ } else if (isprint(c))
+ yyerror("invalid character `%c'\n", c);
+ else
+ yyerror("invalid character `\\%03o'\n", (unsigned char)c);
+ }
+ return 0;
+}
+
+void yywarn(char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (yylineno > 0)
+ fprintf(stderr, "line %d: ", yylineno);
+ fprintf(stderr, "warning: ");
+ vfprintf(stderr, fmt, ap);
+}
diff --git a/src/tools/lcc/lburg/lburg.1 b/src/tools/lcc/lburg/lburg.1
new file mode 100644
index 0000000..8cf7250
--- /dev/null
+++ b/src/tools/lcc/lburg/lburg.1
@@ -0,0 +1,179 @@
+.TH LBURG 1 "local \- 11/30/94"
+.\" $Id: lburg.1 145 2001-10-17 21:53:10Z timo $
+.SH NAME
+lburg \- lcc's code-generator generator
+.SH SYNOPSIS
+.B lburg
+[
+.I option
+]...
+[ [
+.I input
+]
+.I output
+]
+.br
+.SH DESCRIPTION
+.PP
+.I lburg
+reads an lcc-style BURG specification from
+.I input
+and writes a pattern-matching code generator to
+.IR output .
+If
+.I input
+is `\-' or is omitted,
+.I lburg
+reads the standard input;
+If
+.I output
+is `\-' or is omitted,
+.I lburg
+writes to the standard output.
+.PP
+.I lburg
+accepts specifications that conform to the following EBNF grammar.
+Terminals are enclosed in single quotes or are
+given in uppercase, all other symbols are nonterminals or English phrases,
+{X} denotes zero or more instances of X, and [X] denotes an optional X.
+.PP
+.nf
+.RS
+.ft CW
+spec: `%{' configuration `%}' { dcl } `%%' { rule }
+ [ `%%' C code ]
+
+dcl: `%start' nonterm
+ `%term' { ID `=' INT }
+
+rule: nonterm `:' tree template [ C expression ]
+
+tree: term `(' tree `,' tree `)'
+ term `(' tree `)'
+ term
+ nonterm
+
+nonterm: ID
+
+template: `"' { any character except double quote } `"'
+.RE
+.fi
+.PP
+Specifications are structurally similar to
+.IR yacc 's.
+Text between
+`\f(CW%{\fP'
+and
+`\f(CW%}\fP'
+is called the configuration section; there may be several such segments.
+All are concatenated and copied verbatim into the head of the output.
+Text after the second
+`\f(CW%%\fP',
+if any, is also copied verbatim into the output, at the end.
+.PP
+Specifications consist of declarations, a
+`\f(CW%%\fP'
+separator, and rules.
+Input is line-oriented; each declaration and rule must appear on a separate line,
+and declarations must begin in column 1.
+Declarations declare terminals \(em the operators in subject
+trees \(em and associate a unique, positive external symbol
+number with each one.
+Nonterminals are declared by their presence
+on the left side of rules. The
+\f(CW%start\fP
+declaration optionally declares a nonterminal as the start symbol.
+In the grammar above,
+\f(CWterm\fP
+and
+\f(CWnonterm\fP
+denote identifiers that are terminals and nonterminals.
+.PP
+Rules define tree patterns in a fully parenthesized prefix
+form. Every nonterminal denotes a tree.
+Each operator has a fixed
+arity, which is inferred from the rules in which it is used.
+A chain rule is a rule whose pattern is another nonterminal.
+If no start symbol is declared, the nonterminal defined by the first rule is used.
+.PP
+Each rule ends with an expression that computes the cost of matching
+that rule; omitted costs
+default to zero. Costs of chain rules must be constants.
+.PP
+The configuration section configures the output
+for the trees being parsed and the client's environment.
+As shown, this section must define
+\f(CWNODEPTR_TYPE\fP
+to be a visible typedef symbol for a pointer to a
+node in the subject tree.
+The labeller invokes
+\f(CWOP_LABEL(p)\fP,
+\f(CWLEFT\_CHILD(p)\fP, and
+\f(CWRIGHT\_CHILD(p)\fP
+to read the operator and children from the node pointed to by \f(CWp\fP.
+If the configuration section defines these operations as macros, they are implemented in-line;
+otherwise, they must be implemented as functions.
+.PP
+The matcher
+computes and stores a single integral state in each node of the subject tree.
+The configuration section must define a macro
+\f(CWSTATE_LABEL(p)\fP
+to access the state field of the node pointed to
+by \f(CWp\fP. It must be large enough to hold a pointer, and
+a macro is required because it is used as an lvalue.
+.PP
+.SH OPTIONS
+.TP
+.BI \-p \ prefix
+.br
+.ns
+.TP
+.BI \-p prefix
+Use
+.I prefix
+as the disambiquating prefix for visible names and fields.
+The default is `\f(CW_\fP'.
+.TP
+.B \-T
+Arrange for
+.sp
+.nf
+.ft CW
+ void _trace(NODEPTR_TYPE p, int eruleno,
+ int cost, int bestcost);
+.sp
+.fi
+.ft R
+to be called at each successful match.
+\f(CWp\fP
+identifies the node and
+\f(CWeruleno\fP
+identifies the matching rule; the rules are numbered
+beginning at 1 in the order they appear in the input.
+\f(CWcost\fP
+is the cost of the match and
+\f(CWbestcost\fP
+is the cost of the best previous match. The current match
+wins only if
+\f(CWcost\fP
+is less than \f(CWbestcost\fP.
+32767 represents the infinite cost of no previous match.
+\f(CW_trace\fP must be declared in the configuration section.
+.SH "SEE ALSO"
+.IR lcc (1)
+.PP
+C. W. Fraser and D. R. Hanson,
+.IR A Retargetable C Compiler: Design and Implementation ,
+Benjamin/Cummings, Redwood City, CA, 1995,
+ISBN 0-8053-1670-1. Chapter 14.
+.PP
+C. W. Fraser, D. R. Hanson and T. A. Proebsting,
+`Engineering a simple, efficient code generator generator,'
+.I
+ACM Letters on Programming Languages and Systems
+.BR 1 ,
+3 (Sep. 1992), 213-226.
+.br
+.SH BUGS
+Mail bug reports along with the shortest input
+that exposes them to drh@cs.princeton.edu.
diff --git a/src/tools/lcc/lburg/lburg.c b/src/tools/lcc/lburg/lburg.c
new file mode 100644
index 0000000..c43c96a
--- /dev/null
+++ b/src/tools/lcc/lburg/lburg.c
@@ -0,0 +1,671 @@
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "lburg.h"
+
+static char rcsid[] = "lburg.c - faked rcsid";
+
+static char *prefix = "";
+static int Tflag = 0;
+static int ntnumber = 0;
+static Nonterm start = 0;
+static Term terms;
+static Nonterm nts;
+static Rule rules;
+static int nrules;
+static struct block {
+ struct block *link;
+} *memlist; /* list of allocated blocks */
+
+static char *stringf(char *fmt, ...);
+static void print(char *fmt, ...);
+static void ckreach(Nonterm p);
+static void emitclosure(Nonterm nts);
+static void emitcost(Tree t, char *v);
+static void emitdefs(Nonterm nts, int ntnumber);
+static void emitheader(void);
+static void emitkids(Rule rules, int nrules);
+static void emitnts(Rule rules, int nrules);
+static void emitrecalc(char *pre, Term root, Term kid);
+static void emitrecord(char *pre, Rule r, char *c, int cost);
+static void emitrule(Nonterm nts);
+static void emitlabel(Term terms, Nonterm start, int ntnumber);
+static void emitstring(Rule rules);
+static void emitstruct(Nonterm nts, int ntnumber);
+static void emittest(Tree t, char *v, char *suffix);
+
+int main(int argc, char *argv[]) {
+ int c, i;
+ Nonterm p;
+
+ for (i = 1; i < argc; i++)
+ if (strcmp(argv[i], "-T") == 0)
+ Tflag = 1;
+ else if (strncmp(argv[i], "-p", 2) == 0 && argv[i][2])
+ prefix = &argv[i][2];
+ else if (strncmp(argv[i], "-p", 2) == 0 && i + 1 < argc)
+ prefix = argv[++i];
+ else if (*argv[i] == '-' && argv[i][1]) {
+ yyerror("usage: %s [-T | -p prefix]... [ [ input ] output ] \n",
+ argv[0]);
+ exit(1);
+ } else if (infp == NULL) {
+ if (strcmp(argv[i], "-") == 0)
+ infp = stdin;
+ else if ((infp = fopen(argv[i], "r")) == NULL) {
+ yyerror("%s: can't read `%s'\n", argv[0], argv[i]);
+ exit(1);
+ }
+ } else if (outfp == NULL) {
+ if (strcmp(argv[i], "-") == 0)
+ outfp = stdout;
+ if ((outfp = fopen(argv[i], "w")) == NULL) {
+ yyerror("%s: can't write `%s'\n", argv[0], argv[i]);
+ exit(1);
+ }
+ }
+ if (infp == NULL)
+ infp = stdin;
+ if (outfp == NULL)
+ outfp = stdout;
+ yyparse();
+ if (start)
+ ckreach(start);
+ for (p = nts; p; p = p->link) {
+ if (p->rules == NULL)
+ yyerror("undefined nonterminal `%s'\n", p->name);
+ if (!p->reached)
+ yyerror("can't reach nonterminal `%s'\n", p->name);
+ }
+ emitheader();
+ emitdefs(nts, ntnumber);
+ emitstruct(nts, ntnumber);
+ emitnts(rules, nrules);
+ emitstring(rules);
+ emitrule(nts);
+ emitclosure(nts);
+ if (start)
+ emitlabel(terms, start, ntnumber);
+ emitkids(rules, nrules);
+ if (!feof(infp))
+ while ((c = getc(infp)) != EOF)
+ putc(c, outfp);
+ while (memlist) { /* for purify */
+ struct block *q = memlist->link;
+ free(memlist);
+ memlist = q;
+ }
+ return errcnt > 0;
+}
+
+/* alloc - allocate nbytes or issue fatal error */
+void *alloc(int nbytes) {
+ struct block *p = calloc(1, sizeof *p + nbytes);
+
+ if (p == NULL) {
+ yyerror("out of memory\n");
+ exit(1);
+ }
+ p->link = memlist;
+ memlist = p;
+ return p + 1;
+}
+
+/* stringf - format and save a string */
+static char *stringf(char *fmt, ...) {
+ va_list ap;
+ char buf[512];
+
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ va_end(ap);
+ return strcpy(alloc(strlen(buf) + 1), buf);
+}
+
+struct entry {
+ union {
+ char *name;
+ struct term t;
+ struct nonterm nt;
+ } sym;
+ struct entry *link;
+} *table[211];
+#define HASHSIZE (sizeof table/sizeof table[0])
+
+/* hash - return hash number for str */
+static unsigned hash(char *str) {
+ unsigned h = 0;
+
+ while (*str)
+ h = (h<<1) + *str++;
+ return h;
+}
+
+/* lookup - lookup symbol name */
+static void *lookup(char *name) {
+ struct entry *p = table[hash(name)%HASHSIZE];
+
+ for ( ; p; p = p->link)
+ if (strcmp(name, p->sym.name) == 0)
+ return &p->sym;
+ return 0;
+}
+
+/* install - install symbol name */
+static void *install(char *name) {
+ struct entry *p = alloc(sizeof *p);
+ int i = hash(name)%HASHSIZE;
+
+ p->sym.name = name;
+ p->link = table[i];
+ table[i] = p;
+ return &p->sym;
+}
+
+/* nonterm - create a new terminal id, if necessary */
+Nonterm nonterm(char *id) {
+ Nonterm p = lookup(id), *q = &nts;
+
+ if (p && p->kind == NONTERM)
+ return p;
+ if (p && p->kind == TERM)
+ yyerror("`%s' is a terminal\n", id);
+ p = install(id);
+ p->kind = NONTERM;
+ p->number = ++ntnumber;
+ if (p->number == 1)
+ start = p;
+ while (*q && (*q)->number < p->number)
+ q = &(*q)->link;
+ assert(*q == 0 || (*q)->number != p->number);
+ p->link = *q;
+ *q = p;
+ return p;
+}
+
+/* term - create a new terminal id with external symbol number esn */
+Term term(char *id, int esn) {
+ Term p = lookup(id), *q = &terms;
+
+ if (p)
+ yyerror("redefinition of terminal `%s'\n", id);
+ else
+ p = install(id);
+ p->kind = TERM;
+ p->esn = esn;
+ p->arity = -1;
+ while (*q && (*q)->esn < p->esn)
+ q = &(*q)->link;
+ if (*q && (*q)->esn == p->esn)
+ yyerror("duplicate external symbol number `%s=%d'\n",
+ p->name, p->esn);
+ p->link = *q;
+ *q = p;
+ return p;
+}
+
+/* tree - create & initialize a tree node with the given fields */
+Tree tree(char *id, Tree left, Tree right) {
+ Tree t = alloc(sizeof *t);
+ Term p = lookup(id);
+ int arity = 0;
+
+ if (left && right)
+ arity = 2;
+ else if (left)
+ arity = 1;
+ if (p == NULL && arity > 0) {
+ yyerror("undefined terminal `%s'\n", id);
+ p = term(id, -1);
+ } else if (p == NULL && arity == 0)
+ p = (Term)nonterm(id);
+ else if (p && p->kind == NONTERM && arity > 0) {
+ yyerror("`%s' is a nonterminal\n", id);
+ p = term(id, -1);
+ }
+ if (p->kind == TERM && p->arity == -1)
+ p->arity = arity;
+ if (p->kind == TERM && arity != p->arity)
+ yyerror("inconsistent arity for terminal `%s'\n", id);
+ t->op = p;
+ t->nterms = p->kind == TERM;
+ if ((t->left = left) != NULL)
+ t->nterms += left->nterms;
+ if ((t->right = right) != NULL)
+ t->nterms += right->nterms;
+ return t;
+}
+
+/* rule - create & initialize a rule with the given fields */
+Rule rule(char *id, Tree pattern, char *template, char *code) {
+ Rule r = alloc(sizeof *r), *q;
+ Term p = pattern->op;
+ char *end;
+
+ r->lhs = nonterm(id);
+ r->packed = ++r->lhs->lhscount;
+ for (q = &r->lhs->rules; *q; q = &(*q)->decode)
+ ;
+ *q = r;
+ r->pattern = pattern;
+ r->ern = ++nrules;
+ r->template = template;
+ r->code = code;
+ r->cost = strtol(code, &end, 10);
+ if (*end) {
+ r->cost = -1;
+ r->code = stringf("(%s)", code);
+ }
+ if (p->kind == TERM) {
+ for (q = &p->rules; *q; q = &(*q)->next)
+ ;
+ *q = r;
+ } else if (pattern->left == NULL && pattern->right == NULL) {
+ Nonterm p = pattern->op;
+ r->chain = p->chain;
+ p->chain = r;
+ if (r->cost == -1)
+ yyerror("illegal nonconstant cost `%s'\n", code);
+ }
+ for (q = &rules; *q; q = &(*q)->link)
+ ;
+ r->link = *q;
+ *q = r;
+ return r;
+}
+
+/* print - formatted output */
+static void print(char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ for ( ; *fmt; fmt++)
+ if (*fmt == '%')
+ switch (*++fmt) {
+ case 'd': fprintf(outfp, "%d", va_arg(ap, int)); break;
+ case 's': fputs(va_arg(ap, char *), outfp); break;
+ case 'P': fprintf(outfp, "%s_", prefix); break;
+ case 'T': {
+ Tree t = va_arg(ap, Tree);
+ print("%S", t->op);
+ if (t->left && t->right)
+ print("(%T,%T)", t->left, t->right);
+ else if (t->left)
+ print("(%T)", t->left);
+ break;
+ }
+ case 'R': {
+ Rule r = va_arg(ap, Rule);
+ print("%S: %T", r->lhs, r->pattern);
+ break;
+ }
+ case 'S': fputs(va_arg(ap, Term)->name, outfp); break;
+ case '1': case '2': case '3': case '4': case '5': {
+ int n = *fmt - '0';
+ while (n-- > 0)
+ putc('\t', outfp);
+ break;
+ }
+ default: putc(*fmt, outfp); break;
+ }
+ else
+ putc(*fmt, outfp);
+ va_end(ap);
+}
+
+/* reach - mark all nonterminals in tree t as reachable */
+static void reach(Tree t) {
+ Nonterm p = t->op;
+
+ if (p->kind == NONTERM)
+ if (!p->reached)
+ ckreach(p);
+ if (t->left)
+ reach(t->left);
+ if (t->right)
+ reach(t->right);
+}
+
+/* ckreach - mark all nonterminals reachable from p */
+static void ckreach(Nonterm p) {
+ Rule r;
+
+ p->reached = 1;
+ for (r = p->rules; r; r = r->decode)
+ reach(r->pattern);
+}
+
+/* emitcase - emit one case in function state */
+static void emitcase(Term p, int ntnumber) {
+ Rule r;
+
+ print("%1case %d: /* %S */\n", p->esn, p);
+ switch (p->arity) {
+ case 0: case -1:
+ break;
+ case 1:
+ print("%2%Plabel(LEFT_CHILD(a));\n");
+ break;
+ case 2:
+ print("%2%Plabel(LEFT_CHILD(a));\n");
+ print("%2%Plabel(RIGHT_CHILD(a));\n");
+ break;
+ default: assert(0);
+ }
+ for (r = p->rules; r; r = r->next) {
+ char *indent = "\t\t\0";
+ switch (p->arity) {
+ case 0: case -1:
+ print("%2/* %R */\n", r);
+ if (r->cost == -1) {
+ print("%2c = %s;\n", r->code);
+ emitrecord("\t\t", r, "c", 0);
+ } else
+ emitrecord("\t\t", r, r->code, 0);
+ break;
+ case 1:
+ if (r->pattern->nterms > 1) {
+ print("%2if (%1/* %R */\n", r);
+ emittest(r->pattern->left, "LEFT_CHILD(a)", " ");
+ print("%2) {\n");
+ indent = "\t\t\t";
+ } else
+ print("%2/* %R */\n", r);
+ if (r->pattern->nterms == 2 && r->pattern->left
+ && r->pattern->right == NULL)
+ emitrecalc(indent, r->pattern->op, r->pattern->left->op);
+ print("%sc = ", indent);
+ emitcost(r->pattern->left, "LEFT_CHILD(a)");
+ print("%s;\n", r->code);
+ emitrecord(indent, r, "c", 0);
+ if (indent[2])
+ print("%2}\n");
+ break;
+ case 2:
+ if (r->pattern->nterms > 1) {
+ print("%2if (%1/* %R */\n", r);
+ emittest(r->pattern->left, "LEFT_CHILD(a)",
+ r->pattern->right->nterms ? " && " : " ");
+ emittest(r->pattern->right, "RIGHT_CHILD(a)", " ");
+ print("%2) {\n");
+ indent = "\t\t\t";
+ } else
+ print("%2/* %R */\n", r);
+ print("%sc = ", indent);
+ emitcost(r->pattern->left, "LEFT_CHILD(a)");
+ emitcost(r->pattern->right, "RIGHT_CHILD(a)");
+ print("%s;\n", r->code);
+ emitrecord(indent, r, "c", 0);
+ if (indent[2])
+ print("%2}\n");
+ break;
+ default: assert(0);
+ }
+ }
+ print("%2break;\n");
+}
+
+/* emitclosure - emit the closure functions */
+static void emitclosure(Nonterm nts) {
+ Nonterm p;
+
+ for (p = nts; p; p = p->link)
+ if (p->chain)
+ print("static void %Pclosure_%S(NODEPTR_TYPE, int);\n", p);
+ print("\n");
+ for (p = nts; p; p = p->link)
+ if (p->chain) {
+ Rule r;
+ print("static void %Pclosure_%S(NODEPTR_TYPE a, int c) {\n"
+"%1struct %Pstate *p = STATE_LABEL(a);\n", p);
+ for (r = p->chain; r; r = r->chain)
+ emitrecord("\t", r, "c", r->cost);
+ print("}\n\n");
+ }
+}
+
+/* emitcost - emit cost computation for tree t */
+static void emitcost(Tree t, char *v) {
+ Nonterm p = t->op;
+
+ if (p->kind == TERM) {
+ if (t->left)
+ emitcost(t->left, stringf("LEFT_CHILD(%s)", v));
+ if (t->right)
+ emitcost(t->right, stringf("RIGHT_CHILD(%s)", v));
+ } else
+ print("((struct %Pstate *)(%s->x.state))->cost[%P%S_NT] + ", v, p);
+}
+
+/* emitdefs - emit nonterminal defines and data structures */
+static void emitdefs(Nonterm nts, int ntnumber) {
+ Nonterm p;
+
+ for (p = nts; p; p = p->link)
+ print("#define %P%S_NT %d\n", p, p->number);
+ print("\n");
+ print("static char *%Pntname[] = {\n%10,\n");
+ for (p = nts; p; p = p->link)
+ print("%1\"%S\",\n", p);
+ print("%10\n};\n\n");
+}
+
+/* emitheader - emit initial definitions */
+static void emitheader(void) {
+ time_t timer = time(NULL);
+
+ print("/*\ngenerated at %sby %s\n*/\n", ctime(&timer), rcsid);
+ print("static void %Pkids(NODEPTR_TYPE, int, NODEPTR_TYPE[]);\n");
+ print("static void %Plabel(NODEPTR_TYPE);\n");
+ print("static int %Prule(void*, int);\n\n");
+}
+
+/* computekids - compute paths to kids in tree t */
+static char *computekids(Tree t, char *v, char *bp, int *ip) {
+ Term p = t->op;
+
+ if (p->kind == NONTERM) {
+ sprintf(bp, "\t\tkids[%d] = %s;\n", (*ip)++, v);
+ bp += strlen(bp);
+ } else if (p->arity > 0) {
+ bp = computekids(t->left, stringf("LEFT_CHILD(%s)", v), bp, ip);
+ if (p->arity == 2)
+ bp = computekids(t->right, stringf("RIGHT_CHILD(%s)", v), bp, ip);
+ }
+ return bp;
+}
+
+/* emitkids - emit _kids */
+static void emitkids(Rule rules, int nrules) {
+ int i;
+ Rule r, *rc = alloc((nrules + 1 + 1)*sizeof *rc);
+ char **str = alloc((nrules + 1 + 1)*sizeof *str);
+
+ for (i = 0, r = rules; r; r = r->link) {
+ int j = 0;
+ char buf[1024], *bp = buf;
+ *computekids(r->pattern, "p", bp, &j) = 0;
+ for (j = 0; str[j] && strcmp(str[j], buf); j++)
+ ;
+ if (str[j] == NULL)
+ str[j] = strcpy(alloc(strlen(buf) + 1), buf);
+ r->kids = rc[j];
+ rc[j] = r;
+ }
+ print("static void %Pkids(NODEPTR_TYPE p, int eruleno, NODEPTR_TYPE kids[]) {\n"
+"%1if (!p)\n%2fatal(\"%Pkids\", \"Null tree\\n\", 0);\n"
+"%1if (!kids)\n%2fatal(\"%Pkids\", \"Null kids\\n\", 0);\n"
+"%1switch (eruleno) {\n");
+ for (i = 0; (r = rc[i]) != NULL; i++) {
+ for ( ; r; r = r->kids)
+ print("%1case %d: /* %R */\n", r->ern, r);
+ print("%s%2break;\n", str[i]);
+ }
+ print("%1default:\n%2fatal(\"%Pkids\", \"Bad rule number %%d\\n\", eruleno);\n%1}\n}\n\n");
+}
+
+/* emitlabel - emit label function */
+static void emitlabel(Term terms, Nonterm start, int ntnumber) {
+ int i;
+ Term p;
+
+ print("static void %Plabel(NODEPTR_TYPE a) {\n%1int c;\n"
+"%1struct %Pstate *p;\n\n"
+"%1if (!a)\n%2fatal(\"%Plabel\", \"Null tree\\n\", 0);\n");
+ print("%1STATE_LABEL(a) = p = allocate(sizeof *p, FUNC);\n"
+"%1p->rule._stmt = 0;\n");
+ for (i = 1; i <= ntnumber; i++)
+ print("%1p->cost[%d] =\n", i);
+ print("%20x7fff;\n%1switch (OP_LABEL(a)) {\n");
+ for (p = terms; p; p = p->link)
+ emitcase(p, ntnumber);
+ print("%1default:\n"
+"%2fatal(\"%Plabel\", \"Bad terminal %%d\\n\", OP_LABEL(a));\n%1}\n}\n\n");
+}
+
+/* computents - fill in bp with _nts vector for tree t */
+static char *computents(Tree t, char *bp) {
+ if (t) {
+ Nonterm p = t->op;
+ if (p->kind == NONTERM) {
+ sprintf(bp, "%s_%s_NT, ", prefix, p->name);
+ bp += strlen(bp);
+ } else
+ bp = computents(t->right, computents(t->left, bp));
+ }
+ return bp;
+}
+
+/* emitnts - emit _nts ragged array */
+static void emitnts(Rule rules, int nrules) {
+ Rule r;
+ int i, j, *nts = alloc((nrules + 1)*sizeof *nts);
+ char **str = alloc((nrules + 1)*sizeof *str);
+
+ for (i = 0, r = rules; r; r = r->link) {
+ char buf[1024];
+ *computents(r->pattern, buf) = 0;
+ for (j = 0; str[j] && strcmp(str[j], buf); j++)
+ ;
+ if (str[j] == NULL) {
+ print("static short %Pnts_%d[] = { %s0 };\n", j, buf);
+ str[j] = strcpy(alloc(strlen(buf) + 1), buf);
+ }
+ nts[i++] = j;
+ }
+ print("\nstatic short *%Pnts[] = {\n");
+ for (i = j = 0, r = rules; r; r = r->link) {
+ for ( ; j < r->ern; j++)
+ print("%10,%1/* %d */\n", j);
+ print("%1%Pnts_%d,%1/* %d */\n", nts[i++], j++);
+ }
+ print("};\n\n");
+}
+
+/* emitrecalc - emit code that tests for recalculation of INDIR?(VREGP) */
+static void emitrecalc(char *pre, Term root, Term kid) {
+ if (root->kind == TERM && strncmp(root->name, "INDIR", 5) == 0
+ && kid->kind == TERM && strcmp(kid->name, "VREGP" ) == 0) {
+ Nonterm p;
+ print("%sif (mayrecalc(a)) {\n", pre);
+ print("%s%1struct %Pstate *q = a->syms[RX]->u.t.cse->x.state;\n", pre);
+ for (p = nts; p; p = p->link) {
+ print("%s%1if (q->cost[%P%S_NT] == 0) {\n", pre, p);
+ print("%s%2p->cost[%P%S_NT] = 0;\n", pre, p);
+ print("%s%2p->rule.%P%S = q->rule.%P%S;\n", pre, p, p);
+ print("%s%1}\n", pre);
+ }
+ print("%s}\n", pre);
+ }
+}
+
+/* emitrecord - emit code that tests for a winning match of rule r */
+static void emitrecord(char *pre, Rule r, char *c, int cost) {
+ if (Tflag)
+ print("%s%Ptrace(a, %d, %s + %d, p->cost[%P%S_NT]);\n",
+ pre, r->ern, c, cost, r->lhs);
+ print("%sif (", pre);
+ print("%s + %d < p->cost[%P%S_NT]) {\n"
+"%s%1p->cost[%P%S_NT] = %s + %d;\n%s%1p->rule.%P%S = %d;\n",
+ c, cost, r->lhs, pre, r->lhs, c, cost, pre, r->lhs,
+ r->packed);
+ if (r->lhs->chain)
+ print("%s%1%Pclosure_%S(a, %s + %d);\n", pre, r->lhs, c, cost);
+ print("%s}\n", pre);
+}
+
+/* emitrule - emit decoding vectors and _rule */
+static void emitrule(Nonterm nts) {
+ Nonterm p;
+
+ for (p = nts; p; p = p->link) {
+ Rule r;
+ print("static short %Pdecode_%S[] = {\n%10,\n", p);
+ for (r = p->rules; r; r = r->decode)
+ print("%1%d,\n", r->ern);
+ print("};\n\n");
+ }
+ print("static int %Prule(void *state, int goalnt) {\n"
+"%1if (goalnt < 1 || goalnt > %d)\n%2fatal(\"%Prule\", \"Bad goal nonterminal %%d\\n\", goalnt);\n"
+"%1if (!state)\n%2return 0;\n%1switch (goalnt) {\n", ntnumber);
+ for (p = nts; p; p = p->link)
+ print("%1case %P%S_NT:"
+"%1return %Pdecode_%S[((struct %Pstate *)state)->rule.%P%S];\n", p, p, p);
+ print("%1default:\n%2fatal(\"%Prule\", \"Bad goal nonterminal %%d\\n\", goalnt);\n%2return 0;\n%1}\n}\n\n");
+}
+
+/* emitstring - emit arrays of templates, instruction flags, and rules */
+static void emitstring(Rule rules) {
+ Rule r;
+
+ print("static char *%Ptemplates[] = {\n");
+ print("/* 0 */%10,\n");
+ for (r = rules; r; r = r->link)
+ print("/* %d */%1\"%s\",%1/* %R */\n", r->ern, r->template, r);
+ print("};\n");
+ print("\nstatic char %Pisinstruction[] = {\n");
+ print("/* 0 */%10,\n");
+ for (r = rules; r; r = r->link) {
+ int len = strlen(r->template);
+ print("/* %d */%1%d,%1/* %s */\n", r->ern,
+ len >= 2 && r->template[len-2] == '\\' && r->template[len-1] == 'n',
+ r->template);
+ }
+ print("};\n");
+ print("\nstatic char *%Pstring[] = {\n");
+ print("/* 0 */%10,\n");
+ for (r = rules; r; r = r->link)
+ print("/* %d */%1\"%R\",\n", r->ern, r);
+ print("};\n\n");
+}
+
+/* emitstruct - emit the definition of the state structure */
+static void emitstruct(Nonterm nts, int ntnumber) {
+ print("struct %Pstate {\n%1short cost[%d];\n%1struct {\n", ntnumber + 1);
+ for ( ; nts; nts = nts->link) {
+ int n = 1, m = nts->lhscount;
+ while ((m >>= 1) != 0)
+ n++;
+ print("%2unsigned int %P%S:%d;\n", nts, n);
+ }
+ print("%1} rule;\n};\n\n");
+}
+
+/* emittest - emit clause for testing a match */
+static void emittest(Tree t, char *v, char *suffix) {
+ Term p = t->op;
+
+ if (p->kind == TERM) {
+ print("%3%s->op == %d%s/* %S */\n", v, p->esn,
+ t->nterms > 1 ? " && " : suffix, p);
+ if (t->left)
+ emittest(t->left, stringf("LEFT_CHILD(%s)", v),
+ t->right && t->right->nterms ? " && " : suffix);
+ if (t->right)
+ emittest(t->right, stringf("RIGHT_CHILD(%s)", v), suffix);
+ }
+}
diff --git a/src/tools/lcc/lburg/lburg.h b/src/tools/lcc/lburg/lburg.h
new file mode 100644
index 0000000..b67e802
--- /dev/null
+++ b/src/tools/lcc/lburg/lburg.h
@@ -0,0 +1,65 @@
+#ifndef BURG_INCLUDED
+#define BURG_INCLUDED
+
+/* iburg.c: */
+extern void *alloc(int nbytes);
+
+typedef enum { TERM=1, NONTERM } Kind;
+typedef struct rule *Rule;
+typedef struct term *Term;
+struct term { /* terminals: */
+ char *name; /* terminal name */
+ Kind kind; /* TERM */
+ int esn; /* external symbol number */
+ int arity; /* operator arity */
+ Term link; /* next terminal in esn order */
+ Rule rules; /* rules whose pattern starts with term */
+};
+
+typedef struct nonterm *Nonterm;
+struct nonterm { /* nonterminals: */
+ char *name; /* nonterminal name */
+ Kind kind; /* NONTERM */
+ int number; /* identifying number */
+ int lhscount; /* # times nt appears in a rule lhs */
+ int reached; /* 1 iff reached from start nonterminal */
+ Rule rules; /* rules w/nonterminal on lhs */
+ Rule chain; /* chain rules w/nonterminal on rhs */
+ Nonterm link; /* next terminal in number order */
+};
+extern Nonterm nonterm(char *id);
+extern Term term(char *id, int esn);
+
+typedef struct tree *Tree;
+struct tree { /* tree patterns: */
+ void *op; /* a terminal or nonterminal */
+ Tree left, right; /* operands */
+ int nterms; /* number of terminal nodes in this tree */
+};
+extern Tree tree(char *op, Tree left, Tree right);
+
+struct rule { /* rules: */
+ Nonterm lhs; /* lefthand side nonterminal */
+ Tree pattern; /* rule pattern */
+ int ern; /* external rule number */
+ int packed; /* packed external rule number */
+ int cost; /* cost, if a constant */
+ char *code; /* cost, if an expression */
+ char *template; /* assembler template */
+ Rule link; /* next rule in ern order */
+ Rule next; /* next rule with same pattern root */
+ Rule chain; /* next chain rule with same rhs */
+ Rule decode; /* next rule with same lhs */
+ Rule kids; /* next rule with same _kids pattern */
+};
+extern Rule rule(char *id, Tree pattern, char *template, char *code);
+
+/* gram.y: */
+void yyerror(char *fmt, ...);
+int yyparse(void);
+void yywarn(char *fmt, ...);
+extern int errcnt;
+extern FILE *infp;
+extern FILE *outfp;
+
+#endif
diff --git a/src/tools/lcc/src/alloc.c b/src/tools/lcc/src/alloc.c
new file mode 100644
index 0000000..e0566df
--- /dev/null
+++ b/src/tools/lcc/src/alloc.c
@@ -0,0 +1,94 @@
+#include "c.h"
+struct block {
+ struct block *next;
+ char *limit;
+ char *avail;
+};
+union align {
+ long l;
+ char *p;
+ double d;
+ int (*f)(void);
+};
+union header {
+ struct block b;
+ union align a;
+};
+#ifdef PURIFY
+union header *arena[3];
+
+void *allocate(unsigned long n, unsigned a) {
+ union header *new = malloc(sizeof *new + n);
+
+ assert(a < NELEMS(arena));
+ if (new == NULL) {
+ error("insufficient memory\n");
+ exit(1);
+ }
+ new->b.next = (void *)arena[a];
+ arena[a] = new;
+ return new + 1;
+}
+
+void deallocate(unsigned a) {
+ union header *p, *q;
+
+ assert(a < NELEMS(arena));
+ for (p = arena[a]; p; p = q) {
+ q = (void *)p->b.next;
+ free(p);
+ }
+ arena[a] = NULL;
+}
+
+void *newarray(unsigned long m, unsigned long n, unsigned a) {
+ return allocate(m*n, a);
+}
+#else
+static struct block
+ first[] = { { NULL }, { NULL }, { NULL } },
+ *arena[] = { &first[0], &first[1], &first[2] };
+static struct block *freeblocks;
+
+void *allocate(unsigned long n, unsigned a) {
+ struct block *ap;
+
+ assert(a < NELEMS(arena));
+ assert(n > 0);
+ ap = arena[a];
+ n = roundup(n, sizeof (union align));
+ while (n > ap->limit - ap->avail) {
+ if ((ap->next = freeblocks) != NULL) {
+ freeblocks = freeblocks->next;
+ ap = ap->next;
+ } else
+ {
+ unsigned m = sizeof (union header) + n + roundup(10*1024, sizeof (union align));
+ ap->next = malloc(m);
+ ap = ap->next;
+ if (ap == NULL) {
+ error("insufficient memory\n");
+ exit(1);
+ }
+ ap->limit = (char *)ap + m;
+ }
+ ap->avail = (char *)((union header *)ap + 1);
+ ap->next = NULL;
+ arena[a] = ap;
+
+ }
+ ap->avail += n;
+ return ap->avail - n;
+}
+
+void *newarray(unsigned long m, unsigned long n, unsigned a) {
+ return allocate(m*n, a);
+}
+void deallocate(unsigned a) {
+ assert(a < NELEMS(arena));
+ arena[a]->next = freeblocks;
+ freeblocks = first[a].next;
+ first[a].next = NULL;
+ arena[a] = &first[a];
+}
+#endif
diff --git a/src/tools/lcc/src/bind.c b/src/tools/lcc/src/bind.c
new file mode 100644
index 0000000..bc7b983
--- /dev/null
+++ b/src/tools/lcc/src/bind.c
@@ -0,0 +1,8 @@
+#include "c.h"
+extern Interface nullIR;
+extern Interface bytecodeIR;
+Binding bindings[] = {
+ { "null", &nullIR },
+ { "bytecode", &bytecodeIR },
+ { NULL, NULL },
+};
diff --git a/src/tools/lcc/src/bytecode.c b/src/tools/lcc/src/bytecode.c
new file mode 100644
index 0000000..871056a
--- /dev/null
+++ b/src/tools/lcc/src/bytecode.c
@@ -0,0 +1,366 @@
+#include "c.h"
+#define I(f) b_##f
+
+
+static void I(segment)(int n) {
+ static int cseg;
+
+ if (cseg != n)
+ switch (cseg = n) {
+ case CODE: print("code\n"); return;
+ case DATA: print("data\n"); return;
+ case BSS: print("bss\n"); return;
+ case LIT: print("lit\n"); return;
+ default: assert(0);
+ }
+}
+
+static void I(address)(Symbol q, Symbol p, long n) {
+ q->x.name = stringf("%s%s%D", p->x.name, n > 0 ? "+" : "", n);
+}
+
+static void I(defaddress)(Symbol p) {
+ print("address %s\n", p->x.name);
+}
+
+static void I(defconst)(int suffix, int size, Value v) {
+ switch (suffix) {
+ case I:
+ if (size > sizeof (int))
+ print("byte %d %D\n", size, v.i);
+ else
+ print("byte %d %d\n", size, v.i);
+ return;
+ case U:
+ if (size > sizeof (unsigned))
+ print("byte %d %U\n", size, v.u);
+ else
+ print("byte %d %u\n", size, v.u);
+ return;
+ case P: print("byte %d %U\n", size, (unsigned long)v.p); return;
+ case F:
+ if (size == 4) {
+ floatint_t fi;
+ fi.f = v.d;
+ print("byte 4 %u\n", fi.ui);
+ } else {
+ unsigned *p = (unsigned *)&v.d;
+ print("byte 4 %u\n", p[swap]);
+ print("byte 4 %u\n", p[1 - swap]);
+ }
+ return;
+ }
+ assert(0);
+}
+
+static void I(defstring)(int len, char *str) {
+ char *s;
+
+ for (s = str; s < str + len; s++)
+ print("byte 1 %d\n", (*s)&0377);
+}
+
+static void I(defsymbol)(Symbol p) {
+ if (p->scope == CONSTANTS)
+ switch (optype(ttob(p->type))) {
+ case I: p->x.name = stringf("%D", p->u.c.v.i); break;
+ case U: p->x.name = stringf("%U", p->u.c.v.u); break;
+ case P: p->x.name = stringf("%U", p->u.c.v.p); break;
+ case F:
+ { // JDC: added this to get inline floats
+ floatint_t temp;
+
+ temp.f = p->u.c.v.d;
+ p->x.name = stringf("%U", temp.ui );
+ }
+ break;// JDC: added this
+ default: assert(0);
+ }
+ else if (p->scope >= LOCAL && p->sclass == STATIC)
+ p->x.name = stringf("$%d", genlabel(1));
+ else if (p->scope == LABELS || p->generated)
+ p->x.name = stringf("$%s", p->name);
+ else
+ p->x.name = p->name;
+}
+
+static void dumptree(Node p) {
+ switch (specific(p->op)) {
+ case ASGN+B:
+ assert(p->kids[0]);
+ assert(p->kids[1]);
+ assert(p->syms[0]);
+ dumptree(p->kids[0]);
+ dumptree(p->kids[1]);
+ print("%s %d\n", opname(p->op), p->syms[0]->u.c.v.u);
+ return;
+ case RET+V:
+ assert(!p->kids[0]);
+ assert(!p->kids[1]);
+ print("%s\n", opname(p->op));
+ return;
+ }
+ switch (generic(p->op)) {
+ case CNST: case ADDRG: case ADDRF: case ADDRL: case LABEL:
+ assert(!p->kids[0]);
+ assert(!p->kids[1]);
+ assert(p->syms[0] && p->syms[0]->x.name);
+ print("%s %s\n", opname(p->op), p->syms[0]->x.name);
+ return;
+ case CVF: case CVI: case CVP: case CVU:
+ assert(p->kids[0]);
+ assert(!p->kids[1]);
+ assert(p->syms[0]);
+ dumptree(p->kids[0]);
+ print("%s %d\n", opname(p->op), p->syms[0]->u.c.v.i);
+ return;
+ case ARG: case BCOM: case NEG: case INDIR: case JUMP: case RET:
+ assert(p->kids[0]);
+ assert(!p->kids[1]);
+ dumptree(p->kids[0]);
+ print("%s\n", opname(p->op));
+ return;
+ case CALL:
+ assert(p->kids[0]);
+ assert(!p->kids[1]);
+ assert(optype(p->op) != B);
+ dumptree(p->kids[0]);
+ print("%s\n", opname(p->op));
+ if ( !p->count ) { printf("pop\n"); }; // JDC
+ return;
+ case ASGN: case BOR: case BAND: case BXOR: case RSH: case LSH:
+ case ADD: case SUB: case DIV: case MUL: case MOD:
+ assert(p->kids[0]);
+ assert(p->kids[1]);
+ dumptree(p->kids[0]);
+ dumptree(p->kids[1]);
+ print("%s\n", opname(p->op));
+ return;
+ case EQ: case NE: case GT: case GE: case LE: case LT:
+ assert(p->kids[0]);
+ assert(p->kids[1]);
+ assert(p->syms[0]);
+ assert(p->syms[0]->x.name);
+ dumptree(p->kids[0]);
+ dumptree(p->kids[1]);
+ print("%s %s\n", opname(p->op), p->syms[0]->x.name);
+ return;
+ }
+ assert(0);
+}
+
+static void I(emit)(Node p) {
+ for (; p; p = p->link)
+ dumptree(p);
+}
+
+static void I(export)(Symbol p) {
+ print("export %s\n", p->x.name);
+}
+
+static void I(function)(Symbol f, Symbol caller[], Symbol callee[], int ncalls) {
+ int i;
+
+ (*IR->segment)(CODE);
+ offset = 0;
+ for (i = 0; caller[i] && callee[i]; i++) {
+ offset = roundup(offset, caller[i]->type->align);
+ caller[i]->x.name = callee[i]->x.name = stringf("%d", offset);
+ caller[i]->x.offset = callee[i]->x.offset = offset;
+ offset += caller[i]->type->size;
+ }
+ maxargoffset = maxoffset = argoffset = offset = 0;
+ gencode(caller, callee);
+ print("proc %s %d %d\n", f->x.name, maxoffset, maxargoffset);
+ emitcode();
+ print("endproc %s %d %d\n", f->x.name, maxoffset, maxargoffset);
+
+}
+
+static void gen02(Node p) {
+ assert(p);
+ if (generic(p->op) == ARG) {
+ assert(p->syms[0]);
+ argoffset += (p->syms[0]->u.c.v.i < 4 ? 4 : p->syms[0]->u.c.v.i);
+ } else if (generic(p->op) == CALL) {
+ maxargoffset = (argoffset > maxargoffset ? argoffset : maxargoffset);
+ argoffset = 0;
+ }
+}
+
+static void gen01(Node p) {
+ if (p) {
+ gen01(p->kids[0]);
+ gen01(p->kids[1]);
+ gen02(p);
+ }
+}
+
+static Node I(gen)(Node p) {
+ Node q;
+
+ assert(p);
+ for (q = p; q; q = q->link)
+ gen01(q);
+ return p;
+}
+
+static void I(global)(Symbol p) {
+ print("align %d\n", p->type->align > 4 ? 4 : p->type->align);
+ print("LABELV %s\n", p->x.name);
+}
+
+static void I(import)(Symbol p) {
+ print("import %s\n", p->x.name);
+}
+
+static void I(local)(Symbol p) {
+ offset = roundup(offset, p->type->align);
+ p->x.name = stringf("%d", offset);
+ p->x.offset = offset;
+ offset += p->type->size;
+}
+
+static void I(progbeg)(int argc, char *argv[]) {}
+
+static void I(progend)(void) {}
+
+static void I(space)(int n) {
+ print("skip %d\n", n);
+}
+
+//========================================================
+
+// JDC: hacked up to get interleaved source lines in asm code
+static char *sourceFile;
+static char *sourcePtr;
+static int sourceLine;
+
+static int filelength( FILE *f ) {
+ int pos;
+ int end;
+
+ pos = ftell (f);
+ fseek (f, 0, SEEK_END);
+ end = ftell (f);
+ fseek (f, pos, SEEK_SET);
+
+ return end;
+}
+
+static void LoadSourceFile( const char *filename ) {
+ FILE *f;
+ int length;
+
+ f = fopen( filename, "r" );
+ if ( !f ) {
+ print( ";couldn't open %s\n", filename );
+ sourceFile = NULL;
+ return;
+ }
+ length = filelength( f );
+ sourceFile = malloc( length + 1 );
+ if ( sourceFile ) {
+ fread( sourceFile, length, 1, f );
+ sourceFile[length] = 0;
+ }
+
+ fclose( f );
+ sourceLine = 1;
+ sourcePtr = sourceFile;
+}
+
+static void PrintToSourceLine( int line ) {
+ int c;
+
+ if ( !sourceFile ) {
+ return;
+ }
+ while ( sourceLine <= line ) {
+ int i;
+
+ for ( i = 0 ; sourcePtr[i] && sourcePtr[i] != '\n' ; i++ ) {
+ }
+ c = sourcePtr[i];
+ if ( c == '\n' ) {
+ sourcePtr[i] = 0;
+ }
+ print( ";%d:%s\n", sourceLine, sourcePtr );
+ if ( c == 0 ) {
+ sourcePtr += i; // end of file
+ } else {
+ sourcePtr += i+1;
+ }
+ sourceLine++;
+ }
+}
+
+static void I(stabline)(Coordinate *cp) {
+ static char *prevfile;
+ static int prevline;
+
+ if (cp->file && (prevfile == NULL || strcmp(prevfile, cp->file) != 0)) {
+ print("file \"%s\"\n", prevfile = cp->file);
+ prevline = 0;
+ if ( sourceFile ) {
+ free( sourceFile );
+ sourceFile = NULL;
+ }
+ // load the new source file
+ LoadSourceFile( cp->file );
+ }
+ if (cp->y != prevline) {
+ print("line %d\n", prevline = cp->y);
+ PrintToSourceLine( cp->y );
+ }
+}
+
+//========================================================
+
+#define b_blockbeg blockbeg
+#define b_blockend blockend
+
+Interface bytecodeIR = {
+ {1, 1, 0}, /* char */
+ {2, 2, 0}, /* short */
+ {4, 4, 0}, /* int */
+ {4, 4, 0}, /* long */
+ {4, 4, 0}, /* long long */
+ {4, 4, 0}, /* float */ // JDC: use inline floats
+ {4, 4, 0}, /* double */ // JDC: don't ever emit 8 byte double code
+ {4, 4, 0}, /* long double */ // JDC: don't ever emit 8 byte double code
+ {4, 4, 0}, /* T* */
+ {0, 4, 0}, /* struct */
+ 0, /* little_endian */
+ 0, /* mulops_calls */
+ 0, /* wants_callb */
+ 0, /* wants_argb */
+ 1, /* left_to_right */
+ 0, /* wants_dag */
+ 0, /* unsigned_char */
+ I(address),
+ I(blockbeg),
+ I(blockend),
+ I(defaddress),
+ I(defconst),
+ I(defstring),
+ I(defsymbol),
+ I(emit),
+ I(export),
+ I(function),
+ I(gen),
+ I(global),
+ I(import),
+ I(local),
+ I(progbeg),
+ I(progend),
+ I(segment),
+ I(space),
+ 0, /* I(stabblock) */
+ 0, /* I(stabend) */
+ 0, /* I(stabfend) */
+ 0, /* I(stabinit) */
+ I(stabline),
+ 0, /* I(stabsym) */
+ 0, /* I(stabtype) */
+};
diff --git a/src/tools/lcc/src/c.h b/src/tools/lcc/src/c.h
new file mode 100644
index 0000000..43bec08
--- /dev/null
+++ b/src/tools/lcc/src/c.h
@@ -0,0 +1,730 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+
+#define NEW(p,a) ((p) = allocate(sizeof *(p), (a)))
+#define NEW0(p,a) memset(NEW((p),(a)), 0, sizeof *(p))
+#define isaddrop(op) (specific(op)==ADDRG+P || specific(op)==ADDRL+P \
+ || specific(op)==ADDRF+P)
+
+#define MAXLINE 512
+#define BUFSIZE 4096
+
+#define istypename(t,tsym) (kind[t] == CHAR \
+ || (t == ID && tsym && tsym->sclass == TYPEDEF))
+#define sizeop(n) ((n)<<10)
+#define generic(op) ((op)&0x3F0)
+#define specific(op) ((op)&0x3FF)
+#define opindex(op) (((op)>>4)&0x3F)
+#define opkind(op) ((op)&~0x3F0)
+#define opsize(op) ((op)>>10)
+#define optype(op) ((op)&0xF)
+#ifdef __LCC__
+#ifndef __STDC__
+#define __STDC__
+#endif
+#endif
+#define NELEMS(a) ((int)(sizeof (a)/sizeof ((a)[0])))
+#undef roundup
+#define roundup(x,n) (((x)+((n)-1))&(~((n)-1)))
+#define mkop(op,ty) (specific((op) + ttob(ty)))
+
+#define extend(x,ty) ((x)&(1<<(8*(ty)->size-1)) ? (x)|((~0UL)<<(8*(ty)->size-1)) : (x)&ones(8*(ty)->size))
+#define ones(n) ((n)>=8*sizeof (unsigned long) ? ~0UL : ~((~0UL)<<(n)))
+
+#define isqual(t) ((t)->op >= CONST)
+#define unqual(t) (isqual(t) ? (t)->type : (t))
+
+#define isvolatile(t) ((t)->op == VOLATILE \
+ || (t)->op == CONST+VOLATILE)
+#define isconst(t) ((t)->op == CONST \
+ || (t)->op == CONST+VOLATILE)
+#define isarray(t) (unqual(t)->op == ARRAY)
+#define isstruct(t) (unqual(t)->op == STRUCT \
+ || unqual(t)->op == UNION)
+#define isunion(t) (unqual(t)->op == UNION)
+#define isfunc(t) (unqual(t)->op == FUNCTION)
+#define isptr(t) (unqual(t)->op == POINTER)
+#define ischar(t) ((t)->size == 1 && isint(t))
+#define isint(t) (unqual(t)->op == INT \
+ || unqual(t)->op == UNSIGNED)
+#define isfloat(t) (unqual(t)->op == FLOAT)
+#define isarith(t) (unqual(t)->op <= UNSIGNED)
+#define isunsigned(t) (unqual(t)->op == UNSIGNED)
+#define isscalar(t) (unqual(t)->op <= POINTER \
+ || unqual(t)->op == ENUM)
+#define isenum(t) (unqual(t)->op == ENUM)
+#define fieldsize(p) (p)->bitsize
+#define fieldright(p) ((p)->lsb - 1)
+#define fieldleft(p) (8*(p)->type->size - \
+ fieldsize(p) - fieldright(p))
+#define fieldmask(p) (~(~(unsigned)0<<fieldsize(p)))
+typedef struct node *Node;
+
+typedef struct list *List;
+
+typedef struct code *Code;
+
+typedef struct swtch *Swtch;
+
+typedef struct symbol *Symbol;
+
+typedef struct coord {
+ char *file;
+ unsigned x, y;
+} Coordinate;
+typedef struct table *Table;
+
+typedef union value {
+ long i;
+ unsigned long u;
+ double d;
+ void *p;
+ void (*g)(void);
+} Value;
+typedef struct tree *Tree;
+
+typedef struct type *Type;
+
+typedef struct field *Field;
+
+typedef struct {
+ unsigned printed:1;
+ unsigned marked;
+ unsigned short typeno;
+ void *xt;
+} Xtype;
+
+typedef union {
+ float f;
+ int i;
+ unsigned int ui;
+} floatint_t;
+
+#include "config.h"
+typedef struct metrics {
+ unsigned char size, align, outofline;
+} Metrics;
+typedef struct interface {
+ Metrics charmetric;
+ Metrics shortmetric;
+ Metrics intmetric;
+ Metrics longmetric;
+ Metrics longlongmetric;
+ Metrics floatmetric;
+ Metrics doublemetric;
+ Metrics longdoublemetric;
+ Metrics ptrmetric;
+ Metrics structmetric;
+ unsigned little_endian:1;
+ unsigned mulops_calls:1;
+ unsigned wants_callb:1;
+ unsigned wants_argb:1;
+ unsigned left_to_right:1;
+ unsigned wants_dag:1;
+ unsigned unsigned_char:1;
+void (*address)(Symbol p, Symbol q, long n);
+void (*blockbeg)(Env *);
+void (*blockend)(Env *);
+void (*defaddress)(Symbol);
+void (*defconst) (int suffix, int size, Value v);
+void (*defstring)(int n, char *s);
+void (*defsymbol)(Symbol);
+void (*emit) (Node);
+void (*export)(Symbol);
+void (*function)(Symbol, Symbol[], Symbol[], int);
+Node (*gen) (Node);
+void (*global)(Symbol);
+void (*import)(Symbol);
+void (*local)(Symbol);
+void (*progbeg)(int argc, char *argv[]);
+void (*progend)(void);
+void (*segment)(int);
+void (*space)(int);
+void (*stabblock)(int, int, Symbol*);
+void (*stabend) (Coordinate *, Symbol, Coordinate **, Symbol *, Symbol *);
+void (*stabfend) (Symbol, int);
+void (*stabinit) (char *, int, char *[]);
+void (*stabline) (Coordinate *);
+void (*stabsym) (Symbol);
+void (*stabtype) (Symbol);
+ Xinterface x;
+} Interface;
+typedef struct binding {
+ char *name;
+ Interface *ir;
+} Binding;
+
+extern Binding bindings[];
+extern Interface *IR;
+typedef struct {
+ List blockentry;
+ List blockexit;
+ List entry;
+ List exit;
+ List returns;
+ List points;
+ List calls;
+ List end;
+} Events;
+
+enum {
+#define xx(a,b,c,d,e,f,g) a=b,
+#define yy(a,b,c,d,e,f,g)
+#include "token.h"
+ LAST
+};
+struct node {
+ short op;
+ short count;
+ Symbol syms[3];
+ Node kids[2];
+ Node link;
+ Xnode x;
+};
+enum {
+ F=FLOAT,
+ I=INT,
+ U=UNSIGNED,
+ P=POINTER,
+ V=VOID,
+ B=STRUCT
+};
+#define gop(name,value) name=value<<4,
+#define op(name,type,sizes)
+
+enum { gop(CNST,1)
+ op(CNST,F,fdx)
+ op(CNST,I,csilh)
+ op(CNST,P,p)
+ op(CNST,U,csilh)
+ gop(ARG,2)
+ op(ARG,B,-)
+ op(ARG,F,fdx)
+ op(ARG,I,ilh)
+ op(ARG,P,p)
+ op(ARG,U,ilh)
+ gop(ASGN,3)
+ op(ASGN,B,-)
+ op(ASGN,F,fdx)
+ op(ASGN,I,csilh)
+ op(ASGN,P,p)
+ op(ASGN,U,csilh)
+ gop(INDIR,4)
+ op(INDIR,B,-)
+ op(INDIR,F,fdx)
+ op(INDIR,I,csilh)
+ op(INDIR,P,p)
+ op(INDIR,U,csilh)
+ gop(CVF,7)
+ op(CVF,F,fdx)
+ op(CVF,I,ilh)
+ gop(CVI,8)
+ op(CVI,F,fdx)
+ op(CVI,I,csilh)
+ op(CVI,U,csilhp)
+ gop(CVP,9)
+ op(CVP,U,p)
+ gop(CVU,11)
+ op(CVU,I,csilh)
+ op(CVU,P,p)
+ op(CVU,U,csilh)
+ gop(NEG,12)
+ op(NEG,F,fdx)
+ op(NEG,I,ilh)
+ gop(CALL,13)
+ op(CALL,B,-)
+ op(CALL,F,fdx)
+ op(CALL,I,ilh)
+ op(CALL,P,p)
+ op(CALL,U,ilh)
+ op(CALL,V,-)
+ gop(RET,15)
+ op(RET,F,fdx)
+ op(RET,I,ilh)
+ op(RET,P,p)
+ op(RET,U,ilh)
+ op(RET,V,-)
+ gop(ADDRG,16)
+ op(ADDRG,P,p)
+ gop(ADDRF,17)
+ op(ADDRF,P,p)
+ gop(ADDRL,18)
+ op(ADDRL,P,p)
+ gop(ADD,19)
+ op(ADD,F,fdx)
+ op(ADD,I,ilh)
+ op(ADD,P,p)
+ op(ADD,U,ilhp)
+ gop(SUB,20)
+ op(SUB,F,fdx)
+ op(SUB,I,ilh)
+ op(SUB,P,p)
+ op(SUB,U,ilhp)
+ gop(LSH,21)
+ op(LSH,I,ilh)
+ op(LSH,U,ilh)
+ gop(MOD,22)
+ op(MOD,I,ilh)
+ op(MOD,U,ilh)
+ gop(RSH,23)
+ op(RSH,I,ilh)
+ op(RSH,U,ilh)
+ gop(BAND,24)
+ op(BAND,I,ilh)
+ op(BAND,U,ilh)
+ gop(BCOM,25)
+ op(BCOM,I,ilh)
+ op(BCOM,U,ilh)
+ gop(BOR,26)
+ op(BOR,I,ilh)
+ op(BOR,U,ilh)
+ gop(BXOR,27)
+ op(BXOR,I,ilh)
+ op(BXOR,U,ilh)
+ gop(DIV,28)
+ op(DIV,F,fdx)
+ op(DIV,I,ilh)
+ op(DIV,U,ilh)
+ gop(MUL,29)
+ op(MUL,F,fdx)
+ op(MUL,I,ilh)
+ op(MUL,U,ilh)
+ gop(EQ,30)
+ op(EQ,F,fdx)
+ op(EQ,I,ilh)
+ op(EQ,U,ilhp)
+ gop(GE,31)
+ op(GE,F,fdx)
+ op(GE,I,ilh)
+ op(GE,U,ilhp)
+ gop(GT,32)
+ op(GT,F,fdx)
+ op(GT,I,ilh)
+ op(GT,U,ilhp)
+ gop(LE,33)
+ op(LE,F,fdx)
+ op(LE,I,ilh)
+ op(LE,U,ilhp)
+ gop(LT,34)
+ op(LT,F,fdx)
+ op(LT,I,ilh)
+ op(LT,U,ilhp)
+ gop(NE,35)
+ op(NE,F,fdx)
+ op(NE,I,ilh)
+ op(NE,U,ilhp)
+ gop(JUMP,36)
+ op(JUMP,V,-)
+ gop(LABEL,37)
+ op(LABEL,V,-)
+ gop(LOAD,14)
+ op(LOAD,B,-)
+ op(LOAD,F,fdx)
+ op(LOAD,I,csilh)
+ op(LOAD,P,p)
+ op(LOAD,U,csilhp) LASTOP };
+
+#undef gop
+#undef op
+enum { CODE=1, BSS, DATA, LIT };
+enum { PERM=0, FUNC, STMT };
+struct list {
+ void *x;
+ List link;
+};
+
+struct code {
+ enum { Blockbeg, Blockend, Local, Address, Defpoint,
+ Label, Start, Gen, Jump, Switch
+ } kind;
+ Code prev, next;
+ union {
+ struct {
+ int level;
+ Symbol *locals;
+ Table identifiers, types;
+ Env x;
+ } block;
+ Code begin;
+ Symbol var;
+
+ struct {
+ Symbol sym;
+ Symbol base;
+ long offset;
+ } addr;
+ struct {
+ Coordinate src;
+ int point;
+ } point;
+ Node forest;
+ struct {
+ Symbol sym;
+ Symbol table;
+ Symbol deflab;
+ int size;
+ long *values;
+ Symbol *labels;
+ } swtch;
+
+ } u;
+};
+struct swtch {
+ Symbol sym;
+ int lab;
+ Symbol deflab;
+ int ncases;
+ int size;
+ long *values;
+ Symbol *labels;
+};
+struct symbol {
+ char *name;
+ int scope;
+ Coordinate src;
+ Symbol up;
+ List uses;
+ int sclass;
+ unsigned structarg:1;
+
+ unsigned addressed:1;
+ unsigned computed:1;
+ unsigned temporary:1;
+ unsigned generated:1;
+ unsigned defined:1;
+ Type type;
+ float ref;
+ union {
+ struct {
+ int label;
+ Symbol equatedto;
+ } l;
+ struct {
+ unsigned cfields:1;
+ unsigned vfields:1;
+ Table ftab; /* omit */
+ Field flist;
+ } s;
+ int value;
+ Symbol *idlist;
+ struct {
+ Value min, max;
+ } limits;
+ struct {
+ Value v;
+ Symbol loc;
+ } c;
+ struct {
+ Coordinate pt;
+ int label;
+ int ncalls;
+ Symbol *callee;
+ } f;
+ int seg;
+ Symbol alias;
+ struct {
+ Node cse;
+ int replace;
+ Symbol next;
+ } t;
+ } u;
+ Xsymbol x;
+};
+enum { CONSTANTS=1, LABELS, GLOBAL, PARAM, LOCAL };
+struct tree {
+ int op;
+ Type type;
+ Tree kids[2];
+ Node node;
+ union {
+ Value v;
+ Symbol sym;
+
+ Field field;
+ } u;
+};
+enum {
+ AND=38<<4,
+ NOT=39<<4,
+ OR=40<<4,
+ COND=41<<4,
+ RIGHT=42<<4,
+ FIELD=43<<4
+};
+struct type {
+ int op;
+ Type type;
+ int align;
+ int size;
+ union {
+ Symbol sym;
+ struct {
+ unsigned oldstyle:1;
+ Type *proto;
+ } f;
+ } u;
+ Xtype x;
+};
+struct field {
+ char *name;
+ Type type;
+ int offset;
+ short bitsize;
+ short lsb;
+ Field link;
+};
+extern int assignargs;
+extern int prunetemps;
+extern int nodecount;
+extern Symbol cfunc;
+extern Symbol retv;
+extern Tree (*optree[])(int, Tree, Tree);
+
+extern char kind[];
+extern int errcnt;
+extern int errlimit;
+extern int wflag;
+extern Events events;
+extern float refinc;
+
+extern unsigned char *cp;
+extern unsigned char *limit;
+extern char *firstfile;
+extern char *file;
+extern char *line;
+extern int lineno;
+extern int t;
+extern char *token;
+extern Symbol tsym;
+extern Coordinate src;
+extern int Aflag;
+extern int Pflag;
+extern Symbol YYnull;
+extern Symbol YYcheck;
+extern int glevel;
+extern int xref;
+
+extern int ncalled;
+extern int npoints;
+
+extern int needconst;
+extern int explicitCast;
+extern struct code codehead;
+extern Code codelist;
+extern Table stmtlabs;
+extern float density;
+extern Table constants;
+extern Table externals;
+extern Table globals;
+extern Table identifiers;
+extern Table labels;
+extern Table types;
+extern int level;
+
+extern List loci, symbols;
+
+extern List symbols;
+
+extern int where;
+extern Type chartype;
+extern Type doubletype;
+extern Type floattype;
+extern Type inttype;
+extern Type longdouble;
+extern Type longtype;
+extern Type longlong;
+extern Type shorttype;
+extern Type signedchar;
+extern Type unsignedchar;
+extern Type unsignedlonglong;
+extern Type unsignedlong;
+extern Type unsignedshort;
+extern Type unsignedtype;
+extern Type charptype;
+extern Type funcptype;
+extern Type voidptype;
+extern Type voidtype;
+extern Type unsignedptr;
+extern Type signedptr;
+extern Type widechar;
+extern void *allocate(unsigned long n, unsigned a);
+extern void deallocate(unsigned a);
+extern void *newarray(unsigned long m, unsigned long n, unsigned a);
+extern void walk(Tree e, int tlab, int flab);
+extern Node listnodes(Tree e, int tlab, int flab);
+extern Node newnode(int op, Node left, Node right, Symbol p);
+extern Tree cvtconst(Tree);
+extern void printdag(Node, int);
+extern void compound(int, Swtch, int);
+extern void defglobal(Symbol, int);
+extern void finalize(void);
+extern void program(void);
+
+extern Tree vcall(Symbol func, Type ty, ...);
+extern Tree addrof(Tree);
+extern Tree asgn(Symbol, Tree);
+extern Tree asgntree(int, Tree, Tree);
+extern Type assign(Type, Tree);
+extern Tree bittree(int, Tree, Tree);
+extern Tree call(Tree, Type, Coordinate);
+extern Tree calltree(Tree, Type, Tree, Symbol);
+extern Tree condtree(Tree, Tree, Tree);
+extern Tree cnsttree(Type, ...);
+extern Tree consttree(unsigned int, Type);
+extern Tree eqtree(int, Tree, Tree);
+extern int iscallb(Tree);
+extern int isnullptr(Tree);
+extern Tree shtree(int, Tree, Tree);
+extern void typeerror(int, Tree, Tree);
+
+extern void test(int tok, char set[]);
+extern void expect(int tok);
+extern void skipto(int tok, char set[]);
+extern void error(const char *, ...);
+extern int fatal(const char *, const char *, int);
+extern void warning(const char *, ...);
+
+typedef void (*Apply)(void *, void *, void *);
+extern void attach(Apply, void *, List *);
+extern void apply(List event, void *arg1, void *arg2);
+extern Tree retype(Tree p, Type ty);
+extern Tree rightkid(Tree p);
+extern int hascall(Tree p);
+extern Type binary(Type, Type);
+extern Tree cast(Tree, Type);
+extern Tree cond(Tree);
+extern Tree expr0(int);
+extern Tree expr(int);
+extern Tree expr1(int);
+extern Tree field(Tree, const char *);
+extern char *funcname(Tree);
+extern Tree idtree(Symbol);
+extern Tree incr(int, Tree, Tree);
+extern Tree lvalue(Tree);
+extern Tree nullcall(Type, Symbol, Tree, Tree);
+extern Tree pointer(Tree);
+extern Tree rvalue(Tree);
+extern Tree value(Tree);
+
+extern void defpointer(Symbol);
+extern Type initializer(Type, int);
+extern void swtoseg(int);
+
+extern void input_init(int, char *[]);
+extern void fillbuf(void);
+extern void nextline(void);
+
+extern int getchr(void);
+extern int gettok(void);
+
+extern void emitcode(void);
+extern void gencode (Symbol[], Symbol[]);
+extern void fprint(FILE *f, const char *fmt, ...);
+extern char *stringf(const char *, ...);
+extern void check(Node);
+extern void print(const char *, ...);
+
+extern List append(void *x, List list);
+extern int length(List list);
+extern void *ltov (List *list, unsigned a);
+extern void init(int, char *[]);
+
+extern Type typename(void);
+extern void checklab(Symbol p, void *cl);
+extern Type enumdcl(void);
+extern void main_init(int, char *[]);
+extern int main(int, char *[]);
+
+extern void vfprint(FILE *, char *, const char *, va_list);
+
+extern int process(char *);
+extern int findfunc(char *, char *);
+extern int findcount(char *, int, int);
+
+extern Tree constexpr(int);
+extern int intexpr(int, int);
+extern Tree simplify(int, Type, Tree, Tree);
+extern int ispow2(unsigned long u);
+
+extern int reachable(int);
+
+extern void addlocal(Symbol);
+extern void branch(int);
+extern Code code(int);
+extern void definelab(int);
+extern void definept(Coordinate *);
+extern void equatelab(Symbol, Symbol);
+extern Node jump(int);
+extern void retcode(Tree);
+extern void statement(int, Swtch, int);
+extern void swcode(Swtch, int *, int, int);
+extern void swgen(Swtch);
+
+extern char * string(const char *str);
+extern char *stringn(const char *str, int len);
+extern char *stringd(long n);
+extern Symbol relocate(const char *name, Table src, Table dst);
+extern void use(Symbol p, Coordinate src);
+extern void locus(Table tp, Coordinate *cp);
+extern Symbol allsymbols(Table);
+
+extern Symbol constant(Type, Value);
+extern void enterscope(void);
+extern void exitscope(void);
+extern Symbol findlabel(int);
+extern Symbol findtype(Type);
+extern void foreach(Table, int, void (*)(Symbol, void *), void *);
+extern Symbol genident(int, Type, int);
+extern int genlabel(int);
+extern Symbol install(const char *, Table *, int, int);
+extern Symbol intconst(int);
+extern Symbol lookup(const char *, Table);
+extern Symbol mkstr(char *);
+extern Symbol mksymbol(int, const char *, Type);
+extern Symbol newtemp(int, int, int);
+extern Table table(Table, int);
+extern Symbol temporary(int, Type);
+extern char *vtoa(Type, Value);
+
+extern int nodeid(Tree);
+extern char *opname(int);
+extern int *printed(int);
+extern void printtree(Tree, int);
+extern Tree root(Tree);
+extern Tree texpr(Tree (*)(int), int, int);
+extern Tree tree(int, Type, Tree, Tree);
+
+extern void type_init(int, char *[]);
+
+extern Type signedint(Type);
+
+extern int hasproto(Type);
+extern void outtype(Type, FILE *);
+extern void printdecl (Symbol p, Type ty);
+extern void printproto(Symbol p, Symbol args[]);
+extern char *typestring(Type ty, char *id);
+extern Field fieldref(const char *name, Type ty);
+extern Type array(Type, int, int);
+extern Type atop(Type);
+extern Type btot(int, int);
+extern Type compose(Type, Type);
+extern Type deref(Type);
+extern int eqtype(Type, Type, int);
+extern Field fieldlist(Type);
+extern Type freturn(Type);
+extern Type ftype(Type, Type);
+extern Type func(Type, Type *, int);
+extern Field newfield(char *, Type, Type);
+extern Type newstruct(int, char *);
+extern void printtype(Type, int);
+extern Type promote(Type);
+extern Type ptr(Type);
+extern Type qual(int, Type);
+extern void rmtypes(int);
+extern int ttob(Type);
+extern int variadic(Type);
+
diff --git a/src/tools/lcc/src/config.h b/src/tools/lcc/src/config.h
new file mode 100644
index 0000000..6f0d5a6
--- /dev/null
+++ b/src/tools/lcc/src/config.h
@@ -0,0 +1,102 @@
+typedef struct {
+ unsigned char max_unaligned_load;
+ Symbol (*rmap)(int);
+
+ void (*blkfetch)(int size, int off, int reg, int tmp);
+ void (*blkstore)(int size, int off, int reg, int tmp);
+ void (*blkloop)(int dreg, int doff,
+ int sreg, int soff,
+ int size, int tmps[]);
+ void (*_label)(Node);
+ int (*_rule)(void*, int);
+ short **_nts;
+ void (*_kids)(Node, int, Node*);
+ char **_string;
+ char **_templates;
+ char *_isinstruction;
+ char **_ntname;
+ void (*emit2)(Node);
+ void (*doarg)(Node);
+ void (*target)(Node);
+ void (*clobber)(Node);
+} Xinterface;
+extern int askregvar(Symbol, Symbol);
+extern void blkcopy(int, int, int, int, int, int[]);
+extern int getregnum(Node);
+extern int mayrecalc(Node);
+extern int mkactual(int, int);
+extern void mkauto(Symbol);
+extern Symbol mkreg(char *, int, int, int);
+extern Symbol mkwildcard(Symbol *);
+extern int move(Node);
+extern int notarget(Node);
+extern void parseflags(int, char **);
+extern int range(Node, int, int);
+extern unsigned regloc(Symbol); /* omit */
+extern void rtarget(Node, int, Symbol);
+extern void setreg(Node, Symbol);
+extern void spill(unsigned, int, Node);
+extern int widens(Node);
+
+extern int argoffset, maxargoffset;
+extern int bflag, dflag;
+extern int dalign, salign;
+extern int framesize;
+extern unsigned freemask[], usedmask[];
+extern int offset, maxoffset;
+extern int swap;
+extern unsigned tmask[], vmask[];
+typedef struct {
+ unsigned listed:1;
+ unsigned registered:1;
+ unsigned emitted:1;
+ unsigned copy:1;
+ unsigned equatable:1;
+ unsigned spills:1;
+ unsigned mayrecalc:1;
+ void *state;
+ short inst;
+ Node kids[3];
+ Node prev, next;
+ Node prevuse;
+ short argno;
+} Xnode;
+typedef struct {
+ Symbol vbl;
+ short set;
+ short number;
+ unsigned mask;
+} *Regnode;
+enum { IREG=0, FREG=1 };
+typedef struct {
+ char *name;
+ unsigned int eaddr; /* omit */
+ int offset;
+ Node lastuse;
+ int usecount;
+ Regnode regnode;
+ Symbol *wildcard;
+} Xsymbol;
+enum { RX=2 };
+typedef struct {
+ int offset;
+ unsigned freemask[2];
+} Env;
+
+#define LBURG_MAX SHRT_MAX
+
+enum { VREG=(44<<4) };
+
+/* Exported for the front end */
+extern void blockbeg(Env *);
+extern void blockend(Env *);
+extern void emit(Node);
+extern Node gen(Node);
+
+extern unsigned emitbin(Node, int);
+
+#ifdef NDEBUG
+#define debug(x) (void)0
+#else
+#define debug(x) (void)(dflag&&((x),0))
+#endif
diff --git a/src/tools/lcc/src/dag.c b/src/tools/lcc/src/dag.c
new file mode 100644
index 0000000..420cbe7
--- /dev/null
+++ b/src/tools/lcc/src/dag.c
@@ -0,0 +1,736 @@
+#include "c.h"
+
+
+#define iscall(op) (generic(op) == CALL \
+ || (IR->mulops_calls \
+ && (generic(op)==DIV||generic(op)==MOD||generic(op)==MUL) \
+ && ( optype(op)==U || optype(op)==I)))
+static Node forest;
+static struct dag {
+ struct node node;
+ struct dag *hlink;
+} *buckets[16];
+int nodecount;
+static Tree firstarg;
+int assignargs = 1;
+int prunetemps = -1;
+static Node *tail;
+
+static int depth = 0;
+static Node replace(Node);
+static Node prune(Node);
+static Node asgnnode(Symbol, Node);
+static struct dag *dagnode(int, Node, Node, Symbol);
+static Symbol equated(Symbol);
+static void fixup(Node);
+static void labelnode(int);
+static void list(Node);
+static void kill(Symbol);
+static Node node(int, Node, Node, Symbol);
+static void printdag1(Node, int, int);
+static void printnode(Node, int, int);
+static void reset(void);
+static Node tmpnode(Node);
+static void typestab(Symbol, void *);
+static Node undag(Node);
+static Node visit(Node, int);
+static void unlist(void);
+void walk(Tree tp, int tlab, int flab) {
+ listnodes(tp, tlab, flab);
+ if (forest) {
+ Node list = forest->link;
+ forest->link = NULL;
+ if (!IR->wants_dag)
+ list = undag(list);
+ code(Gen)->u.forest = list;
+ forest = NULL;
+ }
+ reset();
+ deallocate(STMT);
+}
+
+static Node node(int op, Node l, Node r, Symbol sym) {
+ int i;
+ struct dag *p;
+
+ i = (opindex(op)^((unsigned long)sym>>2))&(NELEMS(buckets)-1);
+ for (p = buckets[i]; p; p = p->hlink)
+ if (p->node.op == op && p->node.syms[0] == sym
+ && p->node.kids[0] == l && p->node.kids[1] == r)
+ return &p->node;
+ p = dagnode(op, l, r, sym);
+ p->hlink = buckets[i];
+ buckets[i] = p;
+ ++nodecount;
+ return &p->node;
+}
+static struct dag *dagnode(int op, Node l, Node r, Symbol sym) {
+ struct dag *p;
+
+ NEW0(p, FUNC);
+ p->node.op = op;
+ if ((p->node.kids[0] = l) != NULL)
+ ++l->count;
+ if ((p->node.kids[1] = r) != NULL)
+ ++r->count;
+ p->node.syms[0] = sym;
+ return p;
+}
+Node newnode(int op, Node l, Node r, Symbol sym) {
+ return &dagnode(op, l, r, sym)->node;
+}
+static void kill(Symbol p) {
+ int i;
+ struct dag **q;
+
+ for (i = 0; i < NELEMS(buckets); i++)
+ for (q = &buckets[i]; *q; )
+ if (generic((*q)->node.op) == INDIR &&
+ (!isaddrop((*q)->node.kids[0]->op)
+ || (*q)->node.kids[0]->syms[0] == p)) {
+ *q = (*q)->hlink;
+ --nodecount;
+ } else
+ q = &(*q)->hlink;
+}
+static void reset(void) {
+ if (nodecount > 0)
+ memset(buckets, 0, sizeof buckets);
+ nodecount = 0;
+}
+Node listnodes(Tree tp, int tlab, int flab) {
+ Node p = NULL, l, r;
+ int op;
+
+ assert(tlab || flab || (tlab == 0 && flab == 0));
+ if (tp == NULL)
+ return NULL;
+ if (tp->node)
+ return tp->node;
+ op = tp->op + sizeop(tp->type->size);
+ switch (generic(tp->op)) {
+ case AND: { if (depth++ == 0) reset();
+ if (flab) {
+ listnodes(tp->kids[0], 0, flab);
+ listnodes(tp->kids[1], 0, flab);
+ } else {
+ listnodes(tp->kids[0], 0, flab = genlabel(1));
+ listnodes(tp->kids[1], tlab, 0);
+ labelnode(flab);
+ }
+ depth--; } break;
+ case OR: { if (depth++ == 0)
+ reset();
+ if (tlab) {
+ listnodes(tp->kids[0], tlab, 0);
+ listnodes(tp->kids[1], tlab, 0);
+ } else {
+ tlab = genlabel(1);
+ listnodes(tp->kids[0], tlab, 0);
+ listnodes(tp->kids[1], 0, flab);
+ labelnode(tlab);
+ }
+ depth--;
+ } break;
+ case NOT: { return listnodes(tp->kids[0], flab, tlab); }
+ case COND: { Tree q = tp->kids[1];
+ assert(tlab == 0 && flab == 0);
+ if (tp->u.sym)
+ addlocal(tp->u.sym);
+ flab = genlabel(2);
+ listnodes(tp->kids[0], 0, flab);
+ assert(q && q->op == RIGHT);
+ reset();
+ listnodes(q->kids[0], 0, 0);
+ if (forest->op == LABEL+V) {
+ equatelab(forest->syms[0], findlabel(flab + 1));
+ unlist();
+ }
+ list(jump(flab + 1));
+ labelnode(flab);
+ listnodes(q->kids[1], 0, 0);
+ if (forest->op == LABEL+V) {
+ equatelab(forest->syms[0], findlabel(flab + 1));
+ unlist();
+ }
+ labelnode(flab + 1);
+
+ if (tp->u.sym)
+ p = listnodes(idtree(tp->u.sym), 0, 0); } break;
+ case CNST: { Type ty = unqual(tp->type);
+ assert(ty->u.sym);
+ if (tlab || flab) {
+ assert(ty == inttype);
+ if (tlab && tp->u.v.i != 0)
+ list(jump(tlab));
+ else if (flab && tp->u.v.i == 0)
+ list(jump(flab));
+ }
+ else if (ty->u.sym->addressed)
+ p = listnodes(cvtconst(tp), 0, 0);
+ else
+ p = node(op, NULL, NULL, constant(ty, tp->u.v)); } break;
+ case RIGHT: { if ( tp->kids[0] && tp->kids[1]
+ && generic(tp->kids[1]->op) == ASGN
+ && ((generic(tp->kids[0]->op) == INDIR
+ && tp->kids[0]->kids[0] == tp->kids[1]->kids[0])
+ || (tp->kids[0]->op == FIELD
+ && tp->kids[0] == tp->kids[1]->kids[0]))) {
+ assert(tlab == 0 && flab == 0);
+ if (generic(tp->kids[0]->op) == INDIR) {
+ p = listnodes(tp->kids[0], 0, 0);
+ list(p);
+ listnodes(tp->kids[1], 0, 0);
+ }
+ else {
+ assert(generic(tp->kids[0]->kids[0]->op) == INDIR);
+ list(listnodes(tp->kids[0]->kids[0], 0, 0));
+ p = listnodes(tp->kids[0], 0, 0);
+ listnodes(tp->kids[1], 0, 0);
+ }
+ } else if (tp->kids[1]) {
+ listnodes(tp->kids[0], 0, 0);
+ p = listnodes(tp->kids[1], tlab, flab);
+ } else
+ p = listnodes(tp->kids[0], tlab, flab); } break;
+ case JUMP: { assert(tlab == 0 && flab == 0);
+ assert(tp->u.sym == 0);
+ assert(tp->kids[0]);
+ l = listnodes(tp->kids[0], 0, 0);
+ list(newnode(JUMP+V, l, NULL, NULL));
+ reset(); } break;
+ case CALL: { Tree save = firstarg;
+ firstarg = NULL;
+ assert(tlab == 0 && flab == 0);
+ if (tp->op == CALL+B && !IR->wants_callb) {
+ Tree arg0 = tree(ARG+P, tp->kids[1]->type,
+ tp->kids[1], NULL);
+ if (IR->left_to_right)
+ firstarg = arg0;
+ l = listnodes(tp->kids[0], 0, 0);
+ if (!IR->left_to_right || firstarg) {
+ firstarg = NULL;
+ listnodes(arg0, 0, 0);
+ }
+ p = newnode(CALL+V, l, NULL, NULL);
+ } else {
+ l = listnodes(tp->kids[0], 0, 0);
+ r = listnodes(tp->kids[1], 0, 0);
+ p = newnode(tp->op == CALL+B ? tp->op : op, l, r, NULL);
+ }
+ NEW0(p->syms[0], FUNC);
+ assert(isptr(tp->kids[0]->type));
+ assert(isfunc(tp->kids[0]->type->type));
+ p->syms[0]->type = tp->kids[0]->type->type;
+ list(p);
+ reset();
+ cfunc->u.f.ncalls++;
+ firstarg = save;
+ } break;
+ case ARG: { assert(tlab == 0 && flab == 0);
+ if (IR->left_to_right)
+ listnodes(tp->kids[1], 0, 0);
+ if (firstarg) {
+ Tree arg = firstarg;
+ firstarg = NULL;
+ listnodes(arg, 0, 0);
+ }
+ l = listnodes(tp->kids[0], 0, 0);
+ list(newnode(tp->op == ARG+B ? tp->op : op, l, NULL, NULL));
+ forest->syms[0] = intconst(tp->type->size);
+ forest->syms[1] = intconst(tp->type->align);
+ if (!IR->left_to_right)
+ listnodes(tp->kids[1], 0, 0); } break;
+ case EQ: case NE: case GT: case GE: case LE:
+ case LT: { assert(tp->u.sym == 0);
+ assert(errcnt || tlab || flab);
+ l = listnodes(tp->kids[0], 0, 0);
+ r = listnodes(tp->kids[1], 0, 0);
+ assert(errcnt || opkind(l->op) == opkind(r->op));
+ assert(errcnt || optype(op) == optype(l->op));
+ if (tlab)
+ assert(flab == 0),
+ list(newnode(generic(tp->op) + opkind(l->op), l, r, findlabel(tlab)));
+ else if (flab) {
+ switch (generic(tp->op)) {
+ case EQ: op = NE; break;
+ case NE: op = EQ; break;
+ case GT: op = LE; break;
+ case LT: op = GE; break;
+ case GE: op = LT; break;
+ case LE: op = GT; break;
+ default: assert(0);
+ }
+ list(newnode(op + opkind(l->op), l, r, findlabel(flab)));
+ }
+ if (forest && forest->syms[0])
+ forest->syms[0]->ref++; } break;
+ case ASGN: { assert(tlab == 0 && flab == 0);
+ if (tp->kids[0]->op == FIELD) {
+ Tree x = tp->kids[0]->kids[0];
+ Field f = tp->kids[0]->u.field;
+ assert(generic(x->op) == INDIR);
+ reset();
+ l = listnodes(lvalue(x), 0, 0);
+ if (fieldsize(f) < 8*f->type->size) {
+ unsigned int fmask = fieldmask(f);
+ unsigned int mask = fmask<<fieldright(f);
+ Tree q = tp->kids[1];
+ if ((q->op == CNST+I && q->u.v.i == 0)
+ || (q->op == CNST+U && q->u.v.u == 0))
+ q = bittree(BAND, x, cnsttree(unsignedtype, (unsigned long)~mask));
+ else if ((q->op == CNST+I && (q->u.v.i&fmask) == fmask)
+ || (q->op == CNST+U && (q->u.v.u&fmask) == fmask))
+ q = bittree(BOR, x, cnsttree(unsignedtype, (unsigned long)mask));
+ else {
+ listnodes(q, 0, 0);
+ q = bittree(BOR,
+ bittree(BAND, rvalue(lvalue(x)),
+ cnsttree(unsignedtype, (unsigned long)~mask)),
+ bittree(BAND, shtree(LSH, cast(q, unsignedtype),
+ cnsttree(unsignedtype, (unsigned long)fieldright(f))),
+ cnsttree(unsignedtype, (unsigned long)mask)));
+ }
+ r = listnodes(q, 0, 0);
+ op = ASGN + ttob(q->type);
+ } else {
+ r = listnodes(tp->kids[1], 0, 0);
+ op = ASGN + ttob(tp->kids[1]->type);
+ }
+ } else {
+ l = listnodes(tp->kids[0], 0, 0);
+ r = listnodes(tp->kids[1], 0, 0);
+ }
+ list(newnode(tp->op == ASGN+B ? tp->op : op, l, r, NULL));
+ forest->syms[0] = intconst(tp->kids[1]->type->size);
+ forest->syms[1] = intconst(tp->kids[1]->type->align);
+ if (isaddrop(tp->kids[0]->op)
+ && !tp->kids[0]->u.sym->computed)
+ kill(tp->kids[0]->u.sym);
+ else
+ reset();
+ p = listnodes(tp->kids[1], 0, 0); } break;
+ case BOR: case BAND: case BXOR:
+ case ADD: case SUB: case RSH:
+ case LSH: { assert(tlab == 0 && flab == 0);
+ l = listnodes(tp->kids[0], 0, 0);
+ r = listnodes(tp->kids[1], 0, 0);
+ p = node(op, l, r, NULL); } break;
+ case DIV: case MUL:
+ case MOD: { assert(tlab == 0 && flab == 0);
+ l = listnodes(tp->kids[0], 0, 0);
+ r = listnodes(tp->kids[1], 0, 0);
+ p = node(op, l, r, NULL);
+ if (IR->mulops_calls && isint(tp->type)) {
+ list(p);
+ cfunc->u.f.ncalls++;
+ } } break;
+ case RET: { assert(tlab == 0 && flab == 0);
+ l = listnodes(tp->kids[0], 0, 0);
+ list(newnode(op, l, NULL, NULL)); } break;
+ case CVF: case CVI: case CVP:
+ case CVU: { assert(tlab == 0 && flab == 0);
+ assert(optype(tp->kids[0]->op) != optype(tp->op) || tp->kids[0]->type->size != tp->type->size);
+ l = listnodes(tp->kids[0], 0, 0);
+ p = node(op, l, NULL, intconst(tp->kids[0]->type->size));
+ } break;
+ case BCOM:
+ case NEG: { assert(tlab == 0 && flab == 0);
+ l = listnodes(tp->kids[0], 0, 0);
+ p = node(op, l, NULL, NULL); } break;
+ case INDIR: { Type ty = tp->kids[0]->type;
+ assert(tlab == 0 && flab == 0);
+ l = listnodes(tp->kids[0], 0, 0);
+ if (isptr(ty))
+ ty = unqual(ty)->type;
+ if (isvolatile(ty)
+ || (isstruct(ty) && unqual(ty)->u.sym->u.s.vfields))
+ p = newnode(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL);
+ else
+ p = node(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL); } break;
+ case FIELD: { Tree q = tp->kids[0];
+ if (tp->type == inttype) {
+ long n = fieldleft(tp->u.field);
+ q = shtree(RSH,
+ shtree(LSH, q, cnsttree(inttype, n)),
+ cnsttree(inttype, n + fieldright(tp->u.field)));
+ } else if (fieldsize(tp->u.field) < 8*tp->u.field->type->size)
+ q = bittree(BAND,
+ shtree(RSH, q, cnsttree(inttype, (long)fieldright(tp->u.field))),
+ cnsttree(unsignedtype, (unsigned long)fieldmask(tp->u.field)));
+ assert(tlab == 0 && flab == 0);
+ p = listnodes(q, 0, 0); } break;
+ case ADDRG:
+ case ADDRF: { assert(tlab == 0 && flab == 0);
+ p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym);
+ } break;
+ case ADDRL: { assert(tlab == 0 && flab == 0);
+ if (tp->u.sym->temporary)
+ addlocal(tp->u.sym);
+ p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); } break;
+ default:assert(0);
+ }
+ tp->node = p;
+ return p;
+}
+static void list(Node p) {
+ if (p && p->link == NULL) {
+ if (forest) {
+ p->link = forest->link;
+ forest->link = p;
+ } else
+ p->link = p;
+ forest = p;
+ }
+}
+static void labelnode(int lab) {
+ assert(lab);
+ if (forest && forest->op == LABEL+V)
+ equatelab(findlabel(lab), forest->syms[0]);
+ else
+ list(newnode(LABEL+V, NULL, NULL, findlabel(lab)));
+ reset();
+}
+static void unlist(void) {
+ Node p;
+
+ assert(forest);
+ assert(forest != forest->link);
+ p = forest->link;
+ while (p->link != forest)
+ p = p->link;
+ p->link = forest->link;
+ forest = p;
+}
+Tree cvtconst(Tree p) {
+ Symbol q = constant(p->type, p->u.v);
+ Tree e;
+
+ if (q->u.c.loc == NULL)
+ q->u.c.loc = genident(STATIC, p->type, GLOBAL);
+ if (isarray(p->type)) {
+ e = simplify(ADDRG, atop(p->type), NULL, NULL);
+ e->u.sym = q->u.c.loc;
+ } else
+ e = idtree(q->u.c.loc);
+ return e;
+}
+void gencode(Symbol caller[], Symbol callee[]) {
+ Code cp;
+ Coordinate save;
+
+ if (prunetemps == -1)
+ prunetemps = !IR->wants_dag;
+ save = src;
+ if (assignargs) {
+ int i;
+ Symbol p, q;
+ cp = codehead.next->next;
+ codelist = codehead.next;
+ for (i = 0; (p = callee[i]) != NULL
+ && (q = caller[i]) != NULL; i++)
+ if (p->sclass != q->sclass || p->type != q->type)
+ walk(asgn(p, idtree(q)), 0, 0);
+ codelist->next = cp;
+ cp->prev = codelist;
+ }
+ if (glevel && IR->stabsym) {
+ int i;
+ Symbol p, q;
+ for (i = 0; (p = callee[i]) != NULL
+ && (q = caller[i]) != NULL; i++) {
+ (*IR->stabsym)(p);
+ if (p->sclass != q->sclass || p->type != q->type)
+ (*IR->stabsym)(q);
+ }
+ swtoseg(CODE);
+ }
+ cp = codehead.next;
+ for ( ; errcnt <= 0 && cp; cp = cp->next)
+ switch (cp->kind) {
+ case Address: (*IR->address)(cp->u.addr.sym, cp->u.addr.base,
+ cp->u.addr.offset); break;
+ case Blockbeg: {
+ Symbol *p = cp->u.block.locals;
+ (*IR->blockbeg)(&cp->u.block.x);
+ for ( ; *p; p++)
+ if ((*p)->ref != 0.0)
+ (*IR->local)(*p);
+ else if (glevel) (*IR->local)(*p);
+ }
+ break;
+ case Blockend: (*IR->blockend)(&cp->u.begin->u.block.x); break;
+ case Defpoint: src = cp->u.point.src; break;
+ case Gen: case Jump:
+ case Label: if (prunetemps)
+ cp->u.forest = prune(cp->u.forest);
+ fixup(cp->u.forest);
+ cp->u.forest = (*IR->gen)(cp->u.forest); break;
+ case Local: (*IR->local)(cp->u.var); break;
+ case Switch: break;
+ default: assert(0);
+ }
+ src = save;
+}
+static void fixup(Node p) {
+ for ( ; p; p = p->link)
+ switch (generic(p->op)) {
+ case JUMP:
+ if (specific(p->kids[0]->op) == ADDRG+P)
+ p->kids[0]->syms[0] =
+ equated(p->kids[0]->syms[0]);
+ break;
+ case LABEL: assert(p->syms[0] == equated(p->syms[0])); break;
+ case EQ: case GE: case GT: case LE: case LT: case NE:
+ assert(p->syms[0]);
+ p->syms[0] = equated(p->syms[0]);
+ }
+}
+static Symbol equated(Symbol p) {
+ { Symbol q; for (q = p->u.l.equatedto; q; q = q->u.l.equatedto) assert(p != q); }
+ while (p->u.l.equatedto)
+ p = p->u.l.equatedto;
+ return p;
+}
+void emitcode(void) {
+ Code cp;
+ Coordinate save;
+
+ save = src;
+ cp = codehead.next;
+ for ( ; errcnt <= 0 && cp; cp = cp->next)
+ switch (cp->kind) {
+ case Address: break;
+ case Blockbeg: if (glevel && IR->stabblock) {
+ (*IR->stabblock)('{', cp->u.block.level - LOCAL, cp->u.block.locals);
+ swtoseg(CODE);
+ }
+ break;
+ case Blockend: if (glevel && IR->stabblock) {
+ Code bp = cp->u.begin;
+ foreach(bp->u.block.identifiers, bp->u.block.level, typestab, NULL);
+ foreach(bp->u.block.types, bp->u.block.level, typestab, NULL);
+ (*IR->stabblock)('}', bp->u.block.level - LOCAL, bp->u.block.locals);
+ swtoseg(CODE);
+ }
+ break;
+ case Defpoint: src = cp->u.point.src;
+ if (glevel > 0 && IR->stabline) {
+ (*IR->stabline)(&cp->u.point.src); swtoseg(CODE); } break;
+ case Gen: case Jump:
+ case Label: if (cp->u.forest)
+ (*IR->emit)(cp->u.forest); break;
+ case Local: if (glevel && IR->stabsym) {
+ (*IR->stabsym)(cp->u.var);
+ swtoseg(CODE);
+ } break;
+ case Switch: { int i;
+ defglobal(cp->u.swtch.table, LIT);
+ (*IR->defaddress)(equated(cp->u.swtch.labels[0]));
+ for (i = 1; i < cp->u.swtch.size; i++) {
+ long k = cp->u.swtch.values[i-1];
+ while (++k < cp->u.swtch.values[i])
+ assert(k < LONG_MAX),
+ (*IR->defaddress)(equated(cp->u.swtch.deflab));
+ (*IR->defaddress)(equated(cp->u.swtch.labels[i]));
+ }
+ swtoseg(CODE);
+ } break;
+ default: assert(0);
+ }
+ src = save;
+}
+
+static Node undag(Node forest) {
+ Node p;
+
+ tail = &forest;
+ for (p = forest; p; p = p->link)
+ if (generic(p->op) == INDIR) {
+ assert(p->count >= 1);
+ visit(p, 1);
+ if (p->syms[2]) {
+ assert(p->syms[2]->u.t.cse);
+ p->syms[2]->u.t.cse = NULL;
+ addlocal(p->syms[2]);
+ }
+ } else if (iscall(p->op) && p->count >= 1)
+ visit(p, 1);
+ else {
+ assert(p->count == 0),
+ visit(p, 1);
+ *tail = p;
+ tail = &p->link;
+ }
+ *tail = NULL;
+ return forest;
+}
+static Node replace(Node p) {
+ if (p && ( generic(p->op) == INDIR
+ && generic(p->kids[0]->op) == ADDRL
+ && p->kids[0]->syms[0]->temporary
+ && p->kids[0]->syms[0]->u.t.replace)) {
+ p = p->kids[0]->syms[0]->u.t.cse;
+ if (generic(p->op) == INDIR && isaddrop(p->kids[0]->op))
+ p = newnode(p->op, newnode(p->kids[0]->op, NULL, NULL,
+ p->kids[0]->syms[0]), NULL, NULL);
+ else if (generic(p->op) == ADDRG)
+ p = newnode(p->op, NULL, NULL, p->syms[0]);
+ else
+ assert(0);
+ p->count = 1;
+ } else if (p) {
+ p->kids[0] = replace(p->kids[0]);
+ p->kids[1] = replace(p->kids[1]);
+ }
+ return p;
+}
+static Node prune(Node forest) {
+ Node p, *tail = &forest;
+ int count = 0;
+
+ for (p = forest; p; p = p->link) {
+ if (count > 0) {
+ p->kids[0] = replace(p->kids[0]);
+ p->kids[1] = replace(p->kids[1]);
+ }
+ if (( generic(p->op) == ASGN
+ && generic(p->kids[0]->op) == ADDRL
+ && p->kids[0]->syms[0]->temporary
+ && p->kids[0]->syms[0]->u.t.cse == p->kids[1])) {
+ Symbol tmp = p->kids[0]->syms[0];
+ if (!tmp->defined)
+ (*IR->local)(tmp);
+ tmp->defined = 1;
+ if (( generic(p->kids[1]->op) == INDIR
+ && isaddrop(p->kids[1]->kids[0]->op)
+ && p->kids[1]->kids[0]->syms[0]->sclass == REGISTER)
+ || (( generic(p->kids[1]->op) == INDIR
+ && isaddrop(p->kids[1]->kids[0]->op)) && tmp->sclass == AUTO)
+ || (generic(p->kids[1]->op) == ADDRG && tmp->sclass == AUTO)) {
+ tmp->u.t.replace = 1;
+ count++;
+ continue; /* and omit the assignment */
+ }
+ }
+ /* keep the assignment and other roots */
+ *tail = p;
+ tail = &(*tail)->link;
+ }
+ assert(*tail == NULL);
+ return forest;
+}
+static Node visit(Node p, int listed) {
+ if (p) {
+ if (p->syms[2])
+ p = tmpnode(p);
+ else if ((p->count <= 1 && !iscall(p->op))
+ || (p->count == 0 && iscall(p->op))) {
+ p->kids[0] = visit(p->kids[0], 0);
+ p->kids[1] = visit(p->kids[1], 0);
+ }
+
+ else if (specific(p->op) == ADDRL+P || specific(p->op) == ADDRF+P) {
+ assert(!listed);
+ p = newnode(p->op, NULL, NULL, p->syms[0]);
+ p->count = 1;
+ }
+ else if (p->op == INDIR+B) {
+ p = newnode(p->op, p->kids[0], NULL, NULL);
+ p->count = 1;
+ p->kids[0] = visit(p->kids[0], 0);
+ p->kids[1] = visit(p->kids[1], 0);
+ }
+ else {
+ p->kids[0] = visit(p->kids[0], 0);
+ p->kids[1] = visit(p->kids[1], 0);
+ p->syms[2] = temporary(REGISTER, btot(p->op, opsize(p->op)));
+ assert(!p->syms[2]->defined);
+ p->syms[2]->ref = 1;
+ p->syms[2]->u.t.cse = p;
+
+ *tail = asgnnode(p->syms[2], p);
+ tail = &(*tail)->link;
+ if (!listed)
+ p = tmpnode(p);
+ };
+ }
+ return p;
+}
+static Node tmpnode(Node p) {
+ Symbol tmp = p->syms[2];
+
+ assert(tmp);
+ if (--p->count == 0)
+ p->syms[2] = NULL;
+ p = newnode(INDIR + ttob(tmp->type),
+ newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), NULL, NULL);
+ p->count = 1;
+ return p;
+}
+static Node asgnnode(Symbol tmp, Node p) {
+ p = newnode(ASGN + ttob(tmp->type),
+ newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), p, NULL);
+ p->syms[0] = intconst(tmp->type->size);
+ p->syms[1] = intconst(tmp->type->align);
+ return p;
+}
+/* printdag - print dag p on fd, or the node list if p == 0 */
+void printdag(Node p, int fd) {
+ FILE *f = fd == 1 ? stdout : stderr;
+
+ printed(0);
+ if (p == 0) {
+ if ((p = forest) != NULL)
+ do {
+ p = p->link;
+ printdag1(p, fd, 0);
+ } while (p != forest);
+ } else if (*printed(nodeid((Tree)p)))
+ fprint(f, "node'%d printed above\n", nodeid((Tree)p));
+ else
+ printdag1(p, fd, 0);
+}
+
+/* printdag1 - recursively print dag p */
+static void printdag1(Node p, int fd, int lev) {
+ int id, i;
+
+ if (p == 0 || *printed(id = nodeid((Tree)p)))
+ return;
+ *printed(id) = 1;
+ for (i = 0; i < NELEMS(p->kids); i++)
+ printdag1(p->kids[i], fd, lev + 1);
+ printnode(p, fd, lev);
+}
+
+/* printnode - print fields of dag p */
+static void printnode(Node p, int fd, int lev) {
+ if (p) {
+ FILE *f = fd == 1 ? stdout : stderr;
+ int i, id = nodeid((Tree)p);
+ fprint(f, "%c%d%s", lev == 0 ? '\'' : '#', id,
+ &" "[id < 10 ? 0 : id < 100 ? 1 : 2]);
+ fprint(f, "%s count=%d", opname(p->op), p->count);
+ for (i = 0; i < NELEMS(p->kids) && p->kids[i]; i++)
+ fprint(f, " #%d", nodeid((Tree)p->kids[i]));
+ if (generic(p->op) == CALL && p->syms[0] && p->syms[0]->type)
+ fprint(f, " {%t}", p->syms[0]->type);
+ else
+ for (i = 0; i < NELEMS(p->syms) && p->syms[i]; i++)
+ if (p->syms[i]->name)
+ fprint(f, " %s", p->syms[i]->name);
+ else
+ fprint(f, " %p", p->syms[i]);
+ fprint(f, "\n");
+ }
+}
+
+/* typestab - emit stab entries for p */
+static void typestab(Symbol p, void *cl) {
+ if (!isfunc(p->type) && (p->sclass == EXTERN || p->sclass == STATIC) && IR->stabsym)
+ (*IR->stabsym)(p);
+ else if ((p->sclass == TYPEDEF || p->sclass == 0) && IR->stabtype)
+ (*IR->stabtype)(p);
+}
+
diff --git a/src/tools/lcc/src/dagcheck.md b/src/tools/lcc/src/dagcheck.md
new file mode 100644
index 0000000..292dbee
--- /dev/null
+++ b/src/tools/lcc/src/dagcheck.md
@@ -0,0 +1,210 @@
+%{
+#include "c.h"
+typedef Node NODEPTR_TYPE;
+#define OP_LABEL(p) (specific((p)->op))
+#define LEFT_CHILD(p) ((p)->kids[0])
+#define RIGHT_CHILD(p) ((p)->kids[1])
+#define STATE_LABEL(p) ((p)->x.state)
+#define PANIC error
+%}
+%term CNSTF=17 CNSTI=21 CNSTP=23 CNSTU=22
+%term ARGB=41 ARGF=33 ARGI=37 ARGP=39 ARGU=38
+%term ASGNB=57 ASGNF=49 ASGNI=53 ASGNP=55 ASGNU=54
+%term INDIRB=73 INDIRF=65 INDIRI=69 INDIRP=71 INDIRU=70
+%term CVFF=113 CVFI=117
+%term CVIF=129 CVII=133 CVIU=134
+%term CVPP=151 CVPU=150
+%term CVUI=181 CVUP=183 CVUU=182
+%term NEGF=193 NEGI=197
+%term CALLB=217 CALLF=209 CALLI=213 CALLP=215 CALLU=214 CALLV=216
+%term RETF=241 RETI=245 RETP=247 RETU=246 RETV=248
+%term ADDRGP=263
+%term ADDRFP=279
+%term ADDRLP=295
+%term ADDF=305 ADDI=309 ADDP=311 ADDU=310
+%term SUBF=321 SUBI=325 SUBP=327 SUBU=326
+%term LSHI=341 LSHU=342
+%term MODI=357 MODU=358
+%term RSHI=373 RSHU=374
+%term BANDI=389 BANDU=390
+%term BCOMI=405 BCOMU=406
+%term BORI=421 BORU=422
+%term BXORI=437 BXORU=438
+%term DIVF=449 DIVI=453 DIVU=454
+%term MULF=465 MULI=469 MULU=470
+%term EQF=481 EQI=485 EQU=486
+%term GEF=497 GEI=501 GEU=502
+%term GTF=513 GTI=517 GTU=518
+%term LEF=529 LEI=533 LEU=534
+%term LTF=545 LTI=549 LTU=550
+%term NEF=561 NEI=565 NEU=566
+%term JUMPV=584
+%term LABELV=600
+%%
+stmt: INDIRB(P) ""
+stmt: INDIRF(P) ""
+stmt: INDIRI(P) ""
+stmt: INDIRU(P) ""
+stmt: INDIRP(P) ""
+stmt: CALLF(P) ""
+stmt: CALLI(P) ""
+stmt: CALLU(P) ""
+stmt: CALLP(P) ""
+stmt: V ""
+bogus: I "" 1
+bogus: U "" 1
+bogus: P "" 1
+bogus: F "" 1
+bogus: B "" 1
+bogus: V "" 1
+I: bogus "" 1
+U: bogus "" 1
+P: bogus "" 1
+F: bogus "" 1
+B: bogus "" 1
+V: bogus "" 1
+F: CNSTF ""
+I: CNSTI ""
+P: CNSTP ""
+U: CNSTU ""
+V: ARGB(B) ""
+V: ARGF(F) ""
+V: ARGI(I) ""
+V: ARGU(U) ""
+V: ARGP(P) ""
+V: ASGNB(P,B) ""
+V: ASGNF(P,F) ""
+V: ASGNI(P,I) ""
+V: ASGNU(P,U) ""
+V: ASGNP(P,P) ""
+B: INDIRB(P) ""
+F: INDIRF(P) ""
+I: INDIRI(P) ""
+U: INDIRU(P) ""
+P: INDIRP(P) ""
+I: CVII(I) ""
+I: CVUI(U) ""
+I: CVFI(F) ""
+U: CVIU(I) ""
+U: CVUU(U) ""
+U: CVPU(P) ""
+F: CVIF(I) ""
+F: CVFF(F) ""
+P: CVUP(U) ""
+P: CVPP(P) ""
+F: NEGF(F) ""
+I: NEGI(I) ""
+V: CALLB(P,P) ""
+F: CALLF(P) ""
+I: CALLI(P) ""
+U: CALLU(P) ""
+P: CALLP(P) ""
+V: CALLV(P) ""
+V: RETF(F) ""
+V: RETI(I) ""
+V: RETU(U) ""
+V: RETP(P) ""
+V: RETV ""
+P: ADDRGP ""
+P: ADDRFP ""
+P: ADDRLP ""
+F: ADDF(F,F) ""
+I: ADDI(I,I) ""
+P: ADDP(P,I) ""
+P: ADDP(I,P) ""
+P: ADDP(U,P) ""
+P: ADDP(P,U) ""
+U: ADDU(U,U) ""
+F: SUBF(F,F) ""
+I: SUBI(I,I) ""
+P: SUBP(P,I) ""
+P: SUBP(P,U) ""
+U: SUBU(U,U) ""
+I: LSHI(I,I) ""
+U: LSHU(U,I) ""
+I: MODI(I,I) ""
+U: MODU(U,U) ""
+I: RSHI(I,I) ""
+U: RSHU(U,I) ""
+U: BANDU(U,U) ""
+I: BANDI(I,I) ""
+U: BCOMU(U) ""
+I: BCOMI(I) ""
+I: BORI(I,I) ""
+U: BORU(U,U) ""
+U: BXORU(U,U) ""
+I: BXORI(I,I) ""
+F: DIVF(F,F) ""
+I: DIVI(I,I) ""
+U: DIVU(U,U) ""
+F: MULF(F,F) ""
+I: MULI(I,I) ""
+U: MULU(U,U) ""
+V: EQF(F,F) ""
+V: EQI(I,I) ""
+V: EQU(U,U) ""
+V: GEF(F,F) ""
+V: GEI(I,I) ""
+V: GEU(U,U) ""
+V: GTF(F,F) ""
+V: GTI(I,I) ""
+V: GTU(U,U) ""
+V: LEF(F,F) ""
+V: LEI(I,I) ""
+V: LEU(U,U) ""
+V: LTF(F,F) ""
+V: LTI(I,I) ""
+V: LTU(U,U) ""
+V: NEF(F,F) ""
+V: NEI(I,I) ""
+V: NEU(U,U) ""
+V: JUMPV(P) ""
+V: LABELV ""
+%%
+
+static void reduce(NODEPTR_TYPE p, int goalnt) {
+ int i, sz = opsize(p->op), rulenumber = _rule(p->x.state, goalnt);
+ short *nts = _nts[rulenumber];
+ NODEPTR_TYPE kids[10];
+
+ assert(rulenumber);
+ _kids(p, rulenumber, kids);
+ for (i = 0; nts[i]; i++)
+ reduce(kids[i], nts[i]);
+ switch (optype(p->op)) {
+#define xx(ty) if (sz == ty->size) return
+ case I:
+ case U:
+ xx(chartype);
+ xx(shorttype);
+ xx(inttype);
+ xx(longtype);
+ xx(longlong);
+ break;
+ case F:
+ xx(floattype);
+ xx(doubletype);
+ xx(longdouble);
+ break;
+ case P:
+ xx(voidptype);
+ xx(funcptype);
+ break;
+ case V:
+ case B: if (sz == 0) return;
+#undef xx
+ }
+ printdag(p, 2);
+ assert(0);
+}
+
+void check(Node p) {
+ struct _state { short cost[1]; };
+
+ _label(p);
+ if (((struct _state *)p->x.state)->cost[1] > 0) {
+ printdag(p, 2);
+ assert(0);
+ }
+ reduce(p, 1);
+}
diff --git a/src/tools/lcc/src/decl.c b/src/tools/lcc/src/decl.c
new file mode 100644
index 0000000..132241e
--- /dev/null
+++ b/src/tools/lcc/src/decl.c
@@ -0,0 +1,1162 @@
+#include "c.h"
+
+
+#define add(x,n) (x > inttype->u.sym->u.limits.max.i-(n) ? (overflow=1,x) : x+(n))
+#define chkoverflow(x,n) ((void)add(x,n))
+#define bits2bytes(n) (((n) + 7)/8)
+static int regcount;
+
+static List autos, registers;
+Symbol cfunc; /* current function */
+Symbol retv; /* return value location for structs */
+
+static void checkref(Symbol, void *);
+static Symbol dclglobal(int, char *, Type, Coordinate *);
+static Symbol dcllocal(int, char *, Type, Coordinate *);
+static Symbol dclparam(int, char *, Type, Coordinate *);
+static Type dclr(Type, char **, Symbol **, int);
+static Type dclr1(char **, Symbol **, int);
+static void decl(Symbol (*)(int, char *, Type, Coordinate *));
+extern void doconst(Symbol, void *);
+static void doglobal(Symbol, void *);
+static void doextern(Symbol, void *);
+static void exitparams(Symbol []);
+static void fields(Type);
+static void funcdefn(int, char *, Type, Symbol [], Coordinate);
+static void initglobal(Symbol, int);
+static void oldparam(Symbol, void *);
+static Symbol *parameters(Type);
+static Type specifier(int *);
+static Type structdcl(int);
+static Type tnode(int, Type);
+void program(void) {
+ int n;
+
+ level = GLOBAL;
+ for (n = 0; t != EOI; n++)
+ if (kind[t] == CHAR || kind[t] == STATIC
+ || t == ID || t == '*' || t == '(') {
+ decl(dclglobal);
+ deallocate(STMT);
+ if (!(glevel >= 3 || xref))
+ deallocate(FUNC);
+ } else if (t == ';') {
+ warning("empty declaration\n");
+ t = gettok();
+ } else {
+ error("unrecognized declaration\n");
+ t = gettok();
+ }
+ if (n == 0)
+ warning("empty input file\n");
+}
+static Type specifier(int *sclass) {
+ int cls, cons, sign, size, type, vol;
+ Type ty = NULL;
+
+ cls = vol = cons = sign = size = type = 0;
+ if (sclass == NULL)
+ cls = AUTO;
+ for (;;) {
+ int *p, tt = t;
+ switch (t) {
+ case AUTO:
+ case REGISTER: if (level <= GLOBAL && cls == 0)
+ error("invalid use of `%k'\n", t);
+ p = &cls; t = gettok(); break;
+ case STATIC: case EXTERN:
+ case TYPEDEF: p = &cls; t = gettok(); break;
+ case CONST: p = &cons; t = gettok(); break;
+ case VOLATILE: p = &vol; t = gettok(); break;
+ case SIGNED:
+ case UNSIGNED: p = &sign; t = gettok(); break;
+ case LONG: if (size == LONG) {
+ size = 0;
+ tt = LONG+LONG;
+ }
+ p = &size; t = gettok(); break;
+ case SHORT: p = &size; t = gettok(); break;
+ case VOID: case CHAR: case INT: case FLOAT:
+ case DOUBLE: p = &type; ty = tsym->type;
+ t = gettok(); break;
+ case ENUM: p = &type; ty = enumdcl(); break;
+ case STRUCT:
+ case UNION: p = &type; ty = structdcl(t); break;
+ case ID:
+ if (istypename(t, tsym) && type == 0
+ && sign == 0 && size == 0) {
+ use(tsym, src);
+ ty = tsym->type;
+ if (isqual(ty)
+ && ty->size != ty->type->size) {
+ ty = unqual(ty);
+ if (isconst(tsym->type))
+ ty = qual(CONST, ty);
+ if (isvolatile(tsym->type))
+ ty = qual(VOLATILE, ty);
+ tsym->type = ty;
+ }
+ p = &type;
+ t = gettok();
+ } else
+ p = NULL;
+ break;
+ default: p = NULL;
+ }
+ if (p == NULL)
+ break;
+ if (*p)
+ error("invalid use of `%k'\n", tt);
+ *p = tt;
+ }
+ if (sclass)
+ *sclass = cls;
+ if (type == 0) {
+ type = INT;
+ ty = inttype;
+ }
+ if ((size == SHORT && type != INT)
+ || (size == LONG+LONG && type != INT)
+ || (size == LONG && type != INT && type != DOUBLE)
+ || (sign && type != INT && type != CHAR))
+ error("invalid type specification\n");
+ if (type == CHAR && sign)
+ ty = sign == UNSIGNED ? unsignedchar : signedchar;
+ else if (size == SHORT)
+ ty = sign == UNSIGNED ? unsignedshort : shorttype;
+ else if (size == LONG && type == DOUBLE)
+ ty = longdouble;
+ else if (size == LONG+LONG) {
+ ty = sign == UNSIGNED ? unsignedlonglong : longlong;
+ if (Aflag >= 1)
+ warning("`%t' is a non-ANSI type\n", ty);
+ } else if (size == LONG)
+ ty = sign == UNSIGNED ? unsignedlong : longtype;
+ else if (sign == UNSIGNED && type == INT)
+ ty = unsignedtype;
+ if (cons == CONST)
+ ty = qual(CONST, ty);
+ if (vol == VOLATILE)
+ ty = qual(VOLATILE, ty);
+ return ty;
+}
+static void decl(Symbol (*dcl)(int, char *, Type, Coordinate *)) {
+ int sclass;
+ Type ty, ty1;
+ static char stop[] = { CHAR, STATIC, ID, 0 };
+
+ ty = specifier(&sclass);
+ if (t == ID || t == '*' || t == '(' || t == '[') {
+ char *id;
+ Coordinate pos;
+ id = NULL;
+ pos = src;
+ if (level == GLOBAL) {
+ Symbol *params = NULL;
+ ty1 = dclr(ty, &id, &params, 0);
+ if (params && id && isfunc(ty1)
+ && (t == '{' || istypename(t, tsym)
+ || (kind[t] == STATIC && t != TYPEDEF))) {
+ if (sclass == TYPEDEF) {
+ error("invalid use of `typedef'\n");
+ sclass = EXTERN;
+ }
+ if (ty1->u.f.oldstyle)
+ exitscope();
+ funcdefn(sclass, id, ty1, params, pos);
+ return;
+ } else if (params)
+ exitparams(params);
+ } else
+ ty1 = dclr(ty, &id, NULL, 0);
+ for (;;) {
+ if (Aflag >= 1 && !hasproto(ty1))
+ warning("missing prototype\n");
+ if (id == NULL)
+ error("missing identifier\n");
+ else if (sclass == TYPEDEF)
+ {
+ Symbol p = lookup(id, identifiers);
+ if (p && p->scope == level)
+ error("redeclaration of `%s'\n", id);
+ p = install(id, &identifiers, level,
+ level < LOCAL ? PERM : FUNC);
+ p->type = ty1;
+ p->sclass = TYPEDEF;
+ p->src = pos;
+ }
+ else
+ (void)(*dcl)(sclass, id, ty1, &pos);
+ if (t != ',')
+ break;
+ t = gettok();
+ id = NULL;
+ pos = src;
+ ty1 = dclr(ty, &id, NULL, 0);
+ }
+ } else if (ty == NULL
+ || !(isenum(ty) ||
+ (isstruct(ty) && (*unqual(ty)->u.sym->name < '1' || *unqual(ty)->u.sym->name > '9'))))
+ error("empty declaration\n");
+ test(';', stop);
+}
+static Symbol dclglobal(int sclass, char *id, Type ty, Coordinate *pos) {
+ Symbol p;
+
+ if (sclass == 0)
+ sclass = AUTO;
+ else if (sclass != EXTERN && sclass != STATIC) {
+ error("invalid storage class `%k' for `%t %s'\n",
+ sclass, ty, id);
+ sclass = AUTO;
+ }
+ p = lookup(id, identifiers);
+ if (p && p->scope == GLOBAL) {
+ if (p->sclass != TYPEDEF && eqtype(ty, p->type, 1))
+ ty = compose(ty, p->type);
+ else
+ error("redeclaration of `%s' previously declared at %w\n", p->name, &p->src);
+
+ if (!isfunc(ty) && p->defined && t == '=')
+ error("redefinition of `%s' previously defined at %w\n", p->name, &p->src);
+
+ if ((p->sclass == EXTERN && sclass == STATIC)
+ || (p->sclass == STATIC && sclass == AUTO)
+ || (p->sclass == AUTO && sclass == STATIC))
+ warning("inconsistent linkage for `%s' previously declared at %w\n", p->name, &p->src);
+
+ }
+ if (p == NULL || p->scope != GLOBAL) {
+ Symbol q = lookup(id, externals);
+ if (q) {
+ if (sclass == STATIC || !eqtype(ty, q->type, 1))
+ warning("declaration of `%s' does not match previous declaration at %w\n", id, &q->src);
+
+ p = relocate(id, externals, globals);
+ p->sclass = sclass;
+ } else {
+ p = install(id, &globals, GLOBAL, PERM);
+ p->sclass = sclass;
+ (*IR->defsymbol)(p);
+ }
+ if (p->sclass != STATIC) {
+ static int nglobals;
+ nglobals++;
+ if (Aflag >= 2 && nglobals == 512)
+ warning("more than 511 external identifiers\n");
+ }
+ } else if (p->sclass == EXTERN)
+ p->sclass = sclass;
+ p->type = ty;
+ p->src = *pos;
+ if (t == '=' && isfunc(p->type)) {
+ error("illegal initialization for `%s'\n", p->name);
+ t = gettok();
+ initializer(p->type, 0);
+ } else if (t == '=') {
+ initglobal(p, 0);
+ if (glevel > 0 && IR->stabsym) {
+ (*IR->stabsym)(p); swtoseg(p->u.seg); }
+ } else if (p->sclass == STATIC && !isfunc(p->type)
+ && p->type->size == 0)
+ error("undefined size for `%t %s'\n", p->type, p->name);
+ return p;
+}
+static void initglobal(Symbol p, int flag) {
+ Type ty;
+
+ if (t == '=' || flag) {
+ if (p->sclass == STATIC) {
+ for (ty = p->type; isarray(ty); ty = ty->type)
+ ;
+ defglobal(p, isconst(ty) ? LIT : DATA);
+ } else
+ defglobal(p, DATA);
+ if (t == '=')
+ t = gettok();
+ ty = initializer(p->type, 0);
+ if (isarray(p->type) && p->type->size == 0)
+ p->type = ty;
+ if (p->sclass == EXTERN)
+ p->sclass = AUTO;
+ }
+}
+void defglobal(Symbol p, int seg) {
+ p->u.seg = seg;
+ swtoseg(p->u.seg);
+ if (p->sclass != STATIC)
+ (*IR->export)(p);
+ (*IR->global)(p);
+ p->defined = 1;
+}
+
+static Type dclr(Type basety, char **id, Symbol **params, int abstract) {
+ Type ty = dclr1(id, params, abstract);
+
+ for ( ; ty; ty = ty->type)
+ switch (ty->op) {
+ case POINTER:
+ basety = ptr(basety);
+ break;
+ case FUNCTION:
+ basety = func(basety, ty->u.f.proto,
+ ty->u.f.oldstyle);
+ break;
+ case ARRAY:
+ basety = array(basety, ty->size, 0);
+ break;
+ case CONST: case VOLATILE:
+ basety = qual(ty->op, basety);
+ break;
+ default: assert(0);
+ }
+ if (Aflag >= 2 && basety->size > 32767)
+ warning("more than 32767 bytes in `%t'\n", basety);
+ return basety;
+}
+static Type tnode(int op, Type type) {
+ Type ty;
+
+ NEW0(ty, STMT);
+ ty->op = op;
+ ty->type = type;
+ return ty;
+}
+static Type dclr1(char **id, Symbol **params, int abstract) {
+ Type ty = NULL;
+
+ switch (t) {
+ case ID: if (id)
+ *id = token;
+ else
+ error("extraneous identifier `%s'\n", token);
+ t = gettok(); break;
+ case '*': t = gettok(); if (t == CONST || t == VOLATILE) {
+ Type ty1;
+ ty1 = ty = tnode(t, NULL);
+ while ((t = gettok()) == CONST || t == VOLATILE)
+ ty1 = tnode(t, ty1);
+ ty->type = dclr1(id, params, abstract);
+ ty = ty1;
+ } else
+ ty = dclr1(id, params, abstract);
+ ty = tnode(POINTER, ty); break;
+ case '(': t = gettok(); if (abstract
+ && (t == REGISTER || istypename(t, tsym) || t == ')')) {
+ Symbol *args;
+ ty = tnode(FUNCTION, ty);
+ enterscope();
+ if (level > PARAM)
+ enterscope();
+ args = parameters(ty);
+ exitparams(args);
+ } else {
+ ty = dclr1(id, params, abstract);
+ expect(')');
+ if (abstract && ty == NULL
+ && (id == NULL || *id == NULL))
+ return tnode(FUNCTION, NULL);
+ } break;
+ case '[': break;
+ default: return ty;
+ }
+ while (t == '(' || t == '[')
+ switch (t) {
+ case '(': t = gettok(); { Symbol *args;
+ ty = tnode(FUNCTION, ty);
+ enterscope();
+ if (level > PARAM)
+ enterscope();
+ args = parameters(ty);
+ if (params && *params == NULL)
+ *params = args;
+ else
+ exitparams(args);
+ }
+ break;
+ case '[': t = gettok(); { int n = 0;
+ if (kind[t] == ID) {
+ n = intexpr(']', 1);
+ if (n <= 0) {
+ error("`%d' is an illegal array size\n", n);
+ n = 1;
+ }
+ } else
+ expect(']');
+ ty = tnode(ARRAY, ty);
+ ty->size = n; } break;
+ default: assert(0);
+ }
+ return ty;
+}
+static Symbol *parameters(Type fty) {
+ List list = NULL;
+ Symbol *params;
+
+ if (kind[t] == STATIC || istypename(t, tsym)) {
+ int n = 0;
+ Type ty1 = NULL;
+ for (;;) {
+ Type ty;
+ int sclass = 0;
+ char *id = NULL;
+ if (ty1 && t == ELLIPSIS) {
+ static struct symbol sentinel;
+ if (sentinel.type == NULL) {
+ sentinel.type = voidtype;
+ sentinel.defined = 1;
+ }
+ if (ty1 == voidtype)
+ error("illegal formal parameter types\n");
+ list = append(&sentinel, list);
+ t = gettok();
+ break;
+ }
+ if (!istypename(t, tsym) && t != REGISTER)
+ error("missing parameter type\n");
+ n++;
+ ty = dclr(specifier(&sclass), &id, NULL, 1);
+ if ( (ty == voidtype && (ty1 || id))
+ || ty1 == voidtype)
+ error("illegal formal parameter types\n");
+ if (id == NULL)
+ id = stringd(n);
+ if (ty != voidtype)
+ list = append(dclparam(sclass, id, ty, &src), list);
+ if (Aflag >= 1 && !hasproto(ty))
+ warning("missing prototype\n");
+ if (ty1 == NULL)
+ ty1 = ty;
+ if (t != ',')
+ break;
+ t = gettok();
+ }
+ fty->u.f.proto = newarray(length(list) + 1,
+ sizeof (Type *), PERM);
+ params = ltov(&list, FUNC);
+ for (n = 0; params[n]; n++)
+ fty->u.f.proto[n] = params[n]->type;
+ fty->u.f.proto[n] = NULL;
+ fty->u.f.oldstyle = 0;
+ } else {
+ if (t == ID)
+ for (;;) {
+ Symbol p;
+ if (t != ID) {
+ error("expecting an identifier\n");
+ break;
+ }
+ p = dclparam(0, token, inttype, &src);
+ p->defined = 0;
+ list = append(p, list);
+ t = gettok();
+ if (t != ',')
+ break;
+ t = gettok();
+ }
+ params = ltov(&list, FUNC);
+ fty->u.f.proto = NULL;
+ fty->u.f.oldstyle = 1;
+ }
+ if (t != ')') {
+ static char stop[] = { CHAR, STATIC, IF, ')', 0 };
+ expect(')');
+ skipto('{', stop);
+ }
+ if (t == ')')
+ t = gettok();
+ return params;
+}
+static void exitparams(Symbol params[]) {
+ assert(params);
+ if (params[0] && !params[0]->defined)
+ error("extraneous old-style parameter list\n");
+ if (level > PARAM)
+ exitscope();
+ exitscope();
+}
+
+static Symbol dclparam(int sclass, char *id, Type ty, Coordinate *pos) {
+ Symbol p;
+
+ if (isfunc(ty))
+ ty = ptr(ty);
+ else if (isarray(ty))
+ ty = atop(ty);
+ if (sclass == 0)
+ sclass = AUTO;
+ else if (sclass != REGISTER) {
+ error("invalid storage class `%k' for `%t%s\n",
+ sclass, ty, stringf(id ? " %s'" : "' parameter", id));
+ sclass = AUTO;
+ } else if (isvolatile(ty) || isstruct(ty)) {
+ warning("register declaration ignored for `%t%s\n",
+ ty, stringf(id ? " %s'" : "' parameter", id));
+ sclass = AUTO;
+ }
+
+ p = lookup(id, identifiers);
+ if (p && p->scope == level)
+ error("duplicate declaration for `%s' previously declared at %w\n", id, &p->src);
+
+ else
+ p = install(id, &identifiers, level, FUNC);
+ p->sclass = sclass;
+ p->src = *pos;
+ p->type = ty;
+ p->defined = 1;
+ if (t == '=') {
+ error("illegal initialization for parameter `%s'\n", id);
+ t = gettok();
+ (void)expr1(0);
+ }
+ return p;
+}
+static Type structdcl(int op) {
+ char *tag;
+ Type ty;
+ Symbol p;
+ Coordinate pos;
+
+ t = gettok();
+ pos = src;
+ if (t == ID) {
+ tag = token;
+ t = gettok();
+ } else
+ tag = "";
+ if (t == '{') {
+ static char stop[] = { IF, ',', 0 };
+ ty = newstruct(op, tag);
+ ty->u.sym->src = pos;
+ ty->u.sym->defined = 1;
+ t = gettok();
+ if (istypename(t, tsym))
+ fields(ty);
+ else
+ error("invalid %k field declarations\n", op);
+ test('}', stop);
+ }
+ else if (*tag && (p = lookup(tag, types)) != NULL
+ && p->type->op == op) {
+ ty = p->type;
+ if (t == ';' && p->scope < level)
+ ty = newstruct(op, tag);
+ }
+ else {
+ if (*tag == 0)
+ error("missing %k tag\n", op);
+ ty = newstruct(op, tag);
+ }
+ if (*tag && xref)
+ use(ty->u.sym, pos);
+ return ty;
+}
+static void fields(Type ty) {
+ { int n = 0;
+ while (istypename(t, tsym)) {
+ static char stop[] = { IF, CHAR, '}', 0 };
+ Type ty1 = specifier(NULL);
+ for (;;) {
+ Field p;
+ char *id = NULL;
+ Type fty = dclr(ty1, &id, NULL, 0);
+ p = newfield(id, ty, fty);
+ if (Aflag >= 1 && !hasproto(p->type))
+ warning("missing prototype\n");
+ if (t == ':') {
+ if (unqual(p->type) != inttype
+ && unqual(p->type) != unsignedtype) {
+ error("`%t' is an illegal bit-field type\n",
+ p->type);
+ p->type = inttype;
+ }
+ t = gettok();
+ p->bitsize = intexpr(0, 0);
+ if (p->bitsize > 8*inttype->size || p->bitsize < 0) {
+ error("`%d' is an illegal bit-field size\n",
+ p->bitsize);
+ p->bitsize = 8*inttype->size;
+ } else if (p->bitsize == 0 && id) {
+ warning("extraneous 0-width bit field `%t %s' ignored\n", p->type, id);
+
+ p->name = stringd(genlabel(1));
+ }
+ p->lsb = 1;
+ }
+ else {
+ if (id == NULL)
+ error("field name missing\n");
+ else if (isfunc(p->type))
+ error("`%t' is an illegal field type\n", p->type);
+ else if (p->type->size == 0)
+ error("undefined size for field `%t %s'\n",
+ p->type, id);
+ }
+ if (isconst(p->type))
+ ty->u.sym->u.s.cfields = 1;
+ if (isvolatile(p->type))
+ ty->u.sym->u.s.vfields = 1;
+ n++;
+ if (Aflag >= 2 && n == 128)
+ warning("more than 127 fields in `%t'\n", ty);
+ if (t != ',')
+ break;
+ t = gettok();
+ }
+ test(';', stop);
+ } }
+ { int bits = 0, off = 0, overflow = 0;
+ Field p, *q = &ty->u.sym->u.s.flist;
+ ty->align = IR->structmetric.align;
+ for (p = *q; p; p = p->link) {
+ int a = p->type->align ? p->type->align : 1;
+ if (p->lsb)
+ a = unsignedtype->align;
+ if (ty->op == UNION)
+ off = bits = 0;
+ else if (p->bitsize == 0 || bits == 0
+ || bits - 1 + p->bitsize > 8*unsignedtype->size) {
+ off = add(off, bits2bytes(bits-1));
+ bits = 0;
+ chkoverflow(off, a - 1);
+ off = roundup(off, a);
+ }
+ if (a > ty->align)
+ ty->align = a;
+ p->offset = off;
+
+ if (p->lsb) {
+ if (bits == 0)
+ bits = 1;
+ if (IR->little_endian)
+ p->lsb = bits;
+ else
+ p->lsb = 8*unsignedtype->size - bits + 1
+ - p->bitsize + 1;
+ bits += p->bitsize;
+ } else
+ off = add(off, p->type->size);
+ if (off + bits2bytes(bits-1) > ty->size)
+ ty->size = off + bits2bytes(bits-1);
+ if (p->name == NULL
+ || !('1' <= *p->name && *p->name <= '9')) {
+ *q = p;
+ q = &p->link;
+ }
+ }
+ *q = NULL;
+ chkoverflow(ty->size, ty->align - 1);
+ ty->size = roundup(ty->size, ty->align);
+ if (overflow) {
+ error("size of `%t' exceeds %d bytes\n", ty, inttype->u.sym->u.limits.max.i);
+ ty->size = inttype->u.sym->u.limits.max.i&(~(ty->align - 1));
+ } }
+}
+static void funcdefn(int sclass, char *id, Type ty, Symbol params[], Coordinate pt) {
+ int i, n;
+ Symbol *callee, *caller, p;
+ Type rty = freturn(ty);
+
+ if (isstruct(rty) && rty->size == 0)
+ error("illegal use of incomplete type `%t'\n", rty);
+ for (n = 0; params[n]; n++)
+ ;
+ if (n > 0 && params[n-1]->name == NULL)
+ params[--n] = NULL;
+ if (Aflag >= 2 && n > 31)
+ warning("more than 31 parameters in function `%s'\n", id);
+ if (ty->u.f.oldstyle) {
+ if (Aflag >= 1)
+ warning("old-style function definition for `%s'\n", id);
+ caller = params;
+ callee = newarray(n + 1, sizeof *callee, FUNC);
+ memcpy(callee, caller, (n+1)*sizeof *callee);
+ enterscope();
+ assert(level == PARAM);
+ while (kind[t] == STATIC || istypename(t, tsym))
+ decl(dclparam);
+ foreach(identifiers, PARAM, oldparam, callee);
+
+ for (i = 0; (p = callee[i]) != NULL; i++) {
+ if (!p->defined)
+ callee[i] = dclparam(0, p->name, inttype, &p->src);
+ *caller[i] = *p;
+ caller[i]->sclass = AUTO;
+ caller[i]->type = promote(p->type);
+ }
+ p = lookup(id, identifiers);
+ if (p && p->scope == GLOBAL && isfunc(p->type)
+ && p->type->u.f.proto) {
+ Type *proto = p->type->u.f.proto;
+ for (i = 0; caller[i] && proto[i]; i++) {
+ Type ty = unqual(proto[i]);
+ if (eqtype(isenum(ty) ? ty->type : ty,
+ unqual(caller[i]->type), 1) == 0)
+ break;
+ else if (isenum(ty) && !isenum(unqual(caller[i]->type)))
+ warning("compatibility of `%t' and `%t' is compiler dependent\n",
+ proto[i], caller[i]->type);
+ }
+ if (proto[i] || caller[i])
+ error("conflicting argument declarations for function `%s'\n", id);
+
+ }
+ else {
+ Type *proto = newarray(n + 1, sizeof *proto, PERM);
+ if (Aflag >= 1)
+ warning("missing prototype for `%s'\n", id);
+ for (i = 0; i < n; i++)
+ proto[i] = caller[i]->type;
+ proto[i] = NULL;
+ ty = func(rty, proto, 1);
+ }
+ } else {
+ callee = params;
+ caller = newarray(n + 1, sizeof *caller, FUNC);
+ for (i = 0; (p = callee[i]) != NULL && p->name; i++) {
+ NEW(caller[i], FUNC);
+ *caller[i] = *p;
+ if (isint(p->type))
+ caller[i]->type = promote(p->type);
+ caller[i]->sclass = AUTO;
+ if ('1' <= *p->name && *p->name <= '9')
+ error("missing name for parameter %d to function `%s'\n", i + 1, id);
+
+ }
+ caller[i] = NULL;
+ }
+ for (i = 0; (p = callee[i]) != NULL; i++)
+ if (p->type->size == 0) {
+ error("undefined size for parameter `%t %s'\n",
+ p->type, p->name);
+ caller[i]->type = p->type = inttype;
+ }
+ if (Aflag >= 2 && sclass != STATIC && strcmp(id, "main") == 0) {
+ if (ty->u.f.oldstyle)
+ warning("`%t %s()' is a non-ANSI definition\n", rty, id);
+ else if (!(rty == inttype
+ && ((n == 0 && callee[0] == NULL)
+ || (n == 2 && callee[0]->type == inttype
+ && isptr(callee[1]->type) && callee[1]->type->type == charptype
+ && !variadic(ty)))))
+ warning("`%s' is a non-ANSI definition\n", typestring(ty, id));
+ }
+ p = lookup(id, identifiers);
+ if (p && isfunc(p->type) && p->defined)
+ error("redefinition of `%s' previously defined at %w\n",
+ p->name, &p->src);
+ cfunc = dclglobal(sclass, id, ty, &pt);
+ cfunc->u.f.label = genlabel(1);
+ cfunc->u.f.callee = callee;
+ cfunc->u.f.pt = src;
+ cfunc->defined = 1;
+ if (xref)
+ use(cfunc, cfunc->src);
+ if (Pflag)
+ printproto(cfunc, cfunc->u.f.callee);
+ if (ncalled >= 0)
+ ncalled = findfunc(cfunc->name, pt.file);
+ labels = table(NULL, LABELS);
+ stmtlabs = table(NULL, LABELS);
+ refinc = 1.0;
+ regcount = 0;
+ codelist = &codehead;
+ codelist->next = NULL;
+ if (!IR->wants_callb && isstruct(rty))
+ retv = genident(AUTO, ptr(rty), PARAM);
+ compound(0, NULL, 0);
+
+ {
+ Code cp;
+ for (cp = codelist; cp->kind < Label; cp = cp->prev)
+ ;
+ if (cp->kind != Jump) {
+ if (rty != voidtype) {
+ warning("missing return value\n");
+ retcode(cnsttree(inttype, 0L));
+ } else
+ retcode(NULL);
+ }
+ }
+ definelab(cfunc->u.f.label);
+ if (events.exit)
+ apply(events.exit, cfunc, NULL);
+ walk(NULL, 0, 0);
+ exitscope();
+ assert(level == PARAM);
+ foreach(identifiers, level, checkref, NULL);
+ if (!IR->wants_callb && isstruct(rty)) {
+ Symbol *a;
+ a = newarray(n + 2, sizeof *a, FUNC);
+ a[0] = retv;
+ memcpy(&a[1], callee, (n+1)*sizeof *callee);
+ callee = a;
+ a = newarray(n + 2, sizeof *a, FUNC);
+ NEW(a[0], FUNC);
+ *a[0] = *retv;
+ memcpy(&a[1], caller, (n+1)*sizeof *callee);
+ caller = a;
+ }
+ if (!IR->wants_argb)
+ for (i = 0; caller[i]; i++)
+ if (isstruct(caller[i]->type)) {
+ caller[i]->type = ptr(caller[i]->type);
+ callee[i]->type = ptr(callee[i]->type);
+ caller[i]->structarg = callee[i]->structarg = 1;
+ }
+ if (glevel > 1) for (i = 0; callee[i]; i++) callee[i]->sclass = AUTO;
+ if (cfunc->sclass != STATIC)
+ (*IR->export)(cfunc);
+ if (glevel && IR->stabsym) {
+ swtoseg(CODE); (*IR->stabsym)(cfunc); }
+ swtoseg(CODE);
+ (*IR->function)(cfunc, caller, callee, cfunc->u.f.ncalls);
+ if (glevel && IR->stabfend)
+ (*IR->stabfend)(cfunc, lineno);
+ foreach(stmtlabs, LABELS, checklab, NULL);
+ exitscope();
+ expect('}');
+ labels = stmtlabs = NULL;
+ retv = NULL;
+ cfunc = NULL;
+}
+static void oldparam(Symbol p, void *cl) {
+ int i;
+ Symbol *callee = cl;
+
+ for (i = 0; callee[i]; i++)
+ if (p->name == callee[i]->name) {
+ callee[i] = p;
+ return;
+ }
+ error("declared parameter `%s' is missing\n", p->name);
+}
+void compound(int loop, struct swtch *swp, int lev) {
+ Code cp;
+ int nregs;
+
+ walk(NULL, 0, 0);
+ cp = code(Blockbeg);
+ enterscope();
+ assert(level >= LOCAL);
+ if (level == LOCAL && events.entry)
+ apply(events.entry, cfunc, NULL);
+ definept(NULL);
+ expect('{');
+ autos = registers = NULL;
+ if (level == LOCAL && IR->wants_callb
+ && isstruct(freturn(cfunc->type))) {
+ retv = genident(AUTO, ptr(freturn(cfunc->type)), level);
+ retv->defined = 1;
+ retv->ref = 1;
+ registers = append(retv, registers);
+ }
+ while (kind[t] == CHAR || kind[t] == STATIC
+ || (istypename(t, tsym) && getchr() != ':'))
+ decl(dcllocal);
+ {
+ int i;
+ Symbol *a = ltov(&autos, STMT);
+ nregs = length(registers);
+ for (i = 0; a[i]; i++)
+ registers = append(a[i], registers);
+ cp->u.block.locals = ltov(&registers, FUNC);
+ }
+ if (events.blockentry)
+ apply(events.blockentry, cp->u.block.locals, NULL);
+ while (kind[t] == IF || kind[t] == ID)
+ statement(loop, swp, lev);
+ walk(NULL, 0, 0);
+ foreach(identifiers, level, checkref, NULL);
+ {
+ int i = nregs, j;
+ Symbol p;
+ for ( ; (p = cp->u.block.locals[i]) != NULL; i++) {
+ for (j = i; j > nregs
+ && cp->u.block.locals[j-1]->ref < p->ref; j--)
+ cp->u.block.locals[j] = cp->u.block.locals[j-1];
+ cp->u.block.locals[j] = p;
+ }
+ }
+ if (events.blockexit)
+ apply(events.blockexit, cp->u.block.locals, NULL);
+ cp->u.block.level = level;
+ cp->u.block.identifiers = identifiers;
+ cp->u.block.types = types;
+ code(Blockend)->u.begin = cp;
+ if (reachable(Gen))
+ definept(NULL);
+ if (level > LOCAL) {
+ exitscope();
+ expect('}');
+ }
+}
+static void checkref(Symbol p, void *cl) {
+ if (p->scope >= PARAM
+ && (isvolatile(p->type) || isfunc(p->type)))
+ p->addressed = 1;
+ if (Aflag >= 2 && p->defined && p->ref == 0) {
+ if (p->sclass == STATIC)
+ warning("static `%t %s' is not referenced\n",
+ p->type, p->name);
+ else if (p->scope == PARAM)
+ warning("parameter `%t %s' is not referenced\n",
+ p->type, p->name);
+ else if (p->scope >= LOCAL && p->sclass != EXTERN)
+ warning("local `%t %s' is not referenced\n",
+ p->type, p->name);
+ }
+ if (p->sclass == AUTO
+ && ((p->scope == PARAM && regcount == 0)
+ || p->scope >= LOCAL)
+ && !p->addressed && isscalar(p->type) && p->ref >= 3.0)
+ p->sclass = REGISTER;
+ if (level == GLOBAL && p->sclass == STATIC && !p->defined
+ && isfunc(p->type) && p->ref)
+ error("undefined static `%t %s'\n", p->type, p->name);
+ assert(!(level == GLOBAL && p->sclass == STATIC && !p->defined && !isfunc(p->type)));
+}
+static Symbol dcllocal(int sclass, char *id, Type ty, Coordinate *pos) {
+ Symbol p, q;
+
+ if (sclass == 0)
+ sclass = isfunc(ty) ? EXTERN : AUTO;
+ else if (isfunc(ty) && sclass != EXTERN) {
+ error("invalid storage class `%k' for `%t %s'\n",
+ sclass, ty, id);
+ sclass = EXTERN;
+ } else if (sclass == REGISTER
+ && (isvolatile(ty) || isstruct(ty) || isarray(ty))) {
+ warning("register declaration ignored for `%t %s'\n",
+ ty, id);
+ sclass = AUTO;
+ }
+ q = lookup(id, identifiers);
+ if ((q && q->scope >= level)
+ || (q && q->scope == PARAM && level == LOCAL)) {
+ if (sclass == EXTERN && q->sclass == EXTERN
+ && eqtype(q->type, ty, 1))
+ ty = compose(ty, q->type);
+ else
+ error("redeclaration of `%s' previously declared at %w\n", q->name, &q->src);
+ }
+
+ assert(level >= LOCAL);
+ p = install(id, &identifiers, level, sclass == STATIC || sclass == EXTERN ? PERM : FUNC);
+ p->type = ty;
+ p->sclass = sclass;
+ p->src = *pos;
+ switch (sclass) {
+ case EXTERN: q = lookup(id, globals);
+ if (q == NULL || q->sclass == TYPEDEF || q->sclass == ENUM) {
+ q = lookup(id, externals);
+ if (q == NULL) {
+ q = install(p->name, &externals, GLOBAL, PERM);
+ q->type = p->type;
+ q->sclass = EXTERN;
+ q->src = src;
+ (*IR->defsymbol)(q);
+ }
+ }
+ if (!eqtype(p->type, q->type, 1))
+ warning("declaration of `%s' does not match previous declaration at %w\n", q->name, &q->src);
+
+ p->u.alias = q; break;
+ case STATIC: (*IR->defsymbol)(p);
+ initglobal(p, 0);
+ if (!p->defined) {
+ if (p->type->size > 0) {
+ defglobal(p, BSS);
+ (*IR->space)(p->type->size);
+ } else
+ error("undefined size for `%t %s'\n",
+ p->type, p->name);
+ }
+ p->defined = 1; break;
+ case REGISTER: registers = append(p, registers);
+ regcount++;
+ p->defined = 1;
+ break;
+ case AUTO: autos = append(p, autos);
+ p->defined = 1; break;
+ default: assert(0);
+ }
+ if (t == '=') {
+ Tree e;
+ if (sclass == EXTERN)
+ error("illegal initialization of `extern %s'\n", id);
+ t = gettok();
+ definept(NULL);
+ if (isscalar(p->type)
+ || (isstruct(p->type) && t != '{')) {
+ if (t == '{') {
+ t = gettok();
+ e = expr1(0);
+ expect('}');
+ } else
+ e = expr1(0);
+ } else {
+ Symbol t1;
+ Type ty = p->type, ty1 = ty;
+ while (isarray(ty1))
+ ty1 = ty1->type;
+ if (!isconst(ty) && (!isarray(ty) || !isconst(ty1)))
+ ty = qual(CONST, ty);
+ t1 = genident(STATIC, ty, GLOBAL);
+ initglobal(t1, 1);
+ if (isarray(p->type) && p->type->size == 0
+ && t1->type->size > 0)
+ p->type = array(p->type->type,
+ t1->type->size/t1->type->type->size, 0);
+ e = idtree(t1);
+ }
+ walk(root(asgn(p, e)), 0, 0);
+ p->ref = 1;
+ }
+ if (!isfunc(p->type) && p->defined && p->type->size <= 0)
+ error("undefined size for `%t %s'\n", p->type, id);
+ return p;
+}
+void finalize(void) {
+ foreach(externals, GLOBAL, doextern, NULL);
+ foreach(identifiers, GLOBAL, doglobal, NULL);
+ foreach(identifiers, GLOBAL, checkref, NULL);
+ foreach(constants, CONSTANTS, doconst, NULL);
+}
+static void doextern(Symbol p, void *cl) {
+ (*IR->import)(p);
+}
+static void doglobal(Symbol p, void *cl) {
+ if (!p->defined && (p->sclass == EXTERN
+ || (isfunc(p->type) && p->sclass == AUTO)))
+ (*IR->import)(p);
+ else if (!p->defined && !isfunc(p->type)
+ && (p->sclass == AUTO || p->sclass == STATIC)) {
+ if (isarray(p->type)
+ && p->type->size == 0 && p->type->type->size > 0)
+ p->type = array(p->type->type, 1, 0);
+ if (p->type->size > 0) {
+ defglobal(p, BSS);
+ (*IR->space)(p->type->size);
+ if (glevel > 0 && IR->stabsym)
+ (*IR->stabsym)(p);
+ } else
+ error("undefined size for `%t %s'\n",
+ p->type, p->name);
+ p->defined = 1;
+ }
+ if (Pflag
+ && !isfunc(p->type)
+ && !p->generated && p->sclass != EXTERN)
+ printdecl(p, p->type);
+}
+void doconst(Symbol p, void *cl) {
+ if (p->u.c.loc) {
+ assert(p->u.c.loc->u.seg == 0);
+ defglobal(p->u.c.loc, LIT);
+ if (isarray(p->type) && p->type->type == widechar) {
+ unsigned int *s = p->u.c.v.p;
+ int n = p->type->size/widechar->size;
+ while (n-- > 0) {
+ Value v;
+ v.u = *s++;
+ (*IR->defconst)(widechar->op, widechar->size, v);
+ }
+ } else if (isarray(p->type))
+ (*IR->defstring)(p->type->size, p->u.c.v.p);
+ else
+ (*IR->defconst)(p->type->op, p->type->size, p->u.c.v);
+ p->u.c.loc = NULL;
+ }
+}
+void checklab(Symbol p, void *cl) {
+ if (!p->defined)
+ error("undefined label `%s'\n", p->name);
+ p->defined = 1;
+}
+
+Type enumdcl(void) {
+ char *tag;
+ Type ty;
+ Symbol p = {0};
+ Coordinate pos;
+
+ t = gettok();
+ pos = src;
+ if (t == ID) {
+ tag = token;
+ t = gettok();
+ } else
+ tag = "";
+ if (t == '{') {
+ static char follow[] = { IF, 0 };
+ int n = 0;
+ long k = -1;
+ List idlist = 0;
+ ty = newstruct(ENUM, tag);
+ t = gettok();
+ if (t != ID)
+ error("expecting an enumerator identifier\n");
+ while (t == ID) {
+ char *id = token;
+ Coordinate s;
+ if (tsym && tsym->scope == level)
+ error("redeclaration of `%s' previously declared at %w\n",
+ token, &tsym->src);
+ s = src;
+ t = gettok();
+ if (t == '=') {
+ t = gettok();
+ k = intexpr(0, 0);
+ } else {
+ if (k == inttype->u.sym->u.limits.max.i)
+ error("overflow in value for enumeration constant `%s'\n", id);
+ k++;
+ }
+ p = install(id, &identifiers, level, level < LOCAL ? PERM : FUNC);
+ p->src = s;
+ p->type = ty;
+ p->sclass = ENUM;
+ p->u.value = k;
+ idlist = append(p, idlist);
+ n++;
+ if (Aflag >= 2 && n == 128)
+ warning("more than 127 enumeration constants in `%t'\n", ty);
+ if (t != ',')
+ break;
+ t = gettok();
+ if (Aflag >= 2 && t == '}')
+ warning("non-ANSI trailing comma in enumerator list\n");
+ }
+ test('}', follow);
+ ty->type = inttype;
+ ty->size = ty->type->size;
+ ty->align = ty->type->align;
+ ty->u.sym->u.idlist = ltov(&idlist, PERM);
+ ty->u.sym->defined = 1;
+ } else if ((p = lookup(tag, types)) != NULL && p->type->op == ENUM) {
+ ty = p->type;
+ if (t == ';')
+ error("empty declaration\n");
+ } else {
+ error("unknown enumeration `%s'\n", tag);
+ ty = newstruct(ENUM, tag);
+ ty->type = inttype;
+ }
+ if (*tag && xref)
+ use(p, pos);
+ return ty;
+}
+
+Type typename(void) {
+ Type ty = specifier(NULL);
+
+ if (t == '*' || t == '(' || t == '[') {
+ ty = dclr(ty, NULL, NULL, 1);
+ if (Aflag >= 1 && !hasproto(ty))
+ warning("missing prototype\n");
+ }
+ return ty;
+}
+
diff --git a/src/tools/lcc/src/enode.c b/src/tools/lcc/src/enode.c
new file mode 100644
index 0000000..4a37618
--- /dev/null
+++ b/src/tools/lcc/src/enode.c
@@ -0,0 +1,544 @@
+#include "c.h"
+
+
+static Tree addtree(int, Tree, Tree);
+static Tree andtree(int, Tree, Tree);
+static Tree cmptree(int, Tree, Tree);
+static int compatible(Type, Type);
+static Tree multree(int, Tree, Tree);
+static Tree subtree(int, Tree, Tree);
+#define isvoidptr(ty) \
+ (isptr(ty) && unqual(ty->type) == voidtype)
+
+Tree (*optree[])(int, Tree, Tree) = {
+#define xx(a,b,c,d,e,f,g) e,
+#define yy(a,b,c,d,e,f,g) e,
+#include "token.h"
+};
+Tree call(Tree f, Type fty, Coordinate src) {
+ int n = 0;
+ Tree args = NULL, r = NULL, e;
+ Type *proto, rty = unqual(freturn(fty));
+ Symbol t3 = NULL;
+
+ if (fty->u.f.oldstyle)
+ proto = NULL;
+ else
+ proto = fty->u.f.proto;
+ if (hascall(f))
+ r = f;
+ if (isstruct(rty))
+ {
+ t3 = temporary(AUTO, unqual(rty));
+ if (rty->size == 0)
+ error("illegal use of incomplete type `%t'\n", rty);
+ }
+ if (t != ')')
+ for (;;) {
+ Tree q = pointer(expr1(0));
+ if (proto && *proto && *proto != voidtype)
+ {
+ Type aty;
+ q = value(q);
+ aty = assign(*proto, q);
+ if (aty)
+ q = cast(q, aty);
+ else
+ error("type error in argument %d to %s; found `%t' expected `%t'\n", n + 1, funcname(f),
+
+ q->type, *proto);
+ if ((isint(q->type) || isenum(q->type))
+ && q->type->size != inttype->size)
+ q = cast(q, promote(q->type));
+ ++proto;
+ }
+ else
+ {
+ if (!fty->u.f.oldstyle && *proto == NULL)
+ error("too many arguments to %s\n", funcname(f));
+ q = value(q);
+ if (isarray(q->type) || q->type->size == 0)
+ error("type error in argument %d to %s; `%t' is illegal\n", n + 1, funcname(f), q->type);
+
+ else
+ q = cast(q, promote(q->type));
+ }
+ if (!IR->wants_argb && isstruct(q->type)) {
+ if (iscallb(q))
+ q = addrof(q);
+ else {
+ Symbol t1 = temporary(AUTO, unqual(q->type));
+ q = asgn(t1, q);
+ q = tree(RIGHT, ptr(t1->type),
+ root(q), lvalue(idtree(t1)));
+ }
+ }
+ if (q->type->size == 0)
+ q->type = inttype;
+ if (hascall(q))
+ r = r ? tree(RIGHT, voidtype, r, q) : q;
+ args = tree(mkop(ARG, q->type), q->type, q, args);
+ n++;
+ if (Aflag >= 2 && n == 32)
+ warning("more than 31 arguments in a call to %s\n",
+ funcname(f));
+ if (t != ',')
+ break;
+ t = gettok();
+ }
+ expect(')');
+ if (proto && *proto && *proto != voidtype)
+ error("insufficient number of arguments to %s\n",
+ funcname(f));
+ if (r)
+ args = tree(RIGHT, voidtype, r, args);
+ e = calltree(f, rty, args, t3);
+ if (events.calls)
+ apply(events.calls, &src, &e);
+ return e;
+}
+Tree calltree(Tree f, Type ty, Tree args, Symbol t3) {
+ Tree p;
+
+ if (args)
+ f = tree(RIGHT, f->type, args, f);
+ if (isstruct(ty))
+ assert(t3),
+ p = tree(RIGHT, ty,
+ tree(CALL+B, ty, f, addrof(idtree(t3))),
+ idtree(t3));
+ else {
+ Type rty = ty;
+ if (isenum(ty))
+ rty = unqual(ty)->type;
+ if (!isfloat(rty))
+ rty = promote(rty);
+ p = tree(mkop(CALL, rty), rty, f, NULL);
+ if (isptr(ty) || p->type->size > ty->size)
+ p = cast(p, ty);
+ }
+ return p;
+}
+Tree vcall(Symbol func, Type ty, ...) {
+ va_list ap;
+ Tree args = NULL, e, f = pointer(idtree(func)), r = NULL;
+
+ assert(isfunc(func->type));
+ if (ty == NULL)
+ ty = freturn(func->type);
+ va_start(ap, ty);
+ while ((e = va_arg(ap, Tree)) != NULL) {
+ if (hascall(e))
+ r = r == NULL ? e : tree(RIGHT, voidtype, r, e);
+ args = tree(mkop(ARG, e->type), e->type, e, args);
+ }
+ va_end(ap);
+ if (r != NULL)
+ args = tree(RIGHT, voidtype, r, args);
+ return calltree(f, ty, args, NULL);
+}
+int iscallb(Tree e) {
+ return e->op == RIGHT && e->kids[0] && e->kids[1]
+ && e->kids[0]->op == CALL+B
+ && e->kids[1]->op == INDIR+B
+ && isaddrop(e->kids[1]->kids[0]->op)
+ && e->kids[1]->kids[0]->u.sym->temporary;
+}
+
+static Tree addtree(int op, Tree l, Tree r) {
+ Type ty = inttype;
+
+ if (isarith(l->type) && isarith(r->type)) {
+ ty = binary(l->type, r->type);
+ l = cast(l, ty);
+ r = cast(r, ty);
+ } else if (isptr(l->type) && isint(r->type))
+ return addtree(ADD, r, l);
+ else if ( isptr(r->type) && isint(l->type)
+ && !isfunc(r->type->type))
+ {
+ long n;
+ ty = unqual(r->type);
+ n = unqual(ty->type)->size;
+ if (n == 0)
+ error("unknown size for type `%t'\n", ty->type);
+ l = cast(l, promote(l->type));
+ if (n > 1)
+ l = multree(MUL, cnsttree(signedptr, n), l);
+ if (YYcheck && !isaddrop(r->op)) /* omit */
+ return nullcall(ty, YYcheck, r, l); /* omit */
+ return simplify(ADD, ty, l, r);
+ }
+
+ else
+ typeerror(op, l, r);
+ return simplify(op, ty, l, r);
+}
+
+Tree cnsttree(Type ty, ...) {
+ Tree p = tree(mkop(CNST,ty), ty, NULL, NULL);
+ va_list ap;
+
+ va_start(ap, ty);
+ switch (ty->op) {
+ case INT: p->u.v.i = va_arg(ap, long); break;
+ case UNSIGNED:p->u.v.u = va_arg(ap, unsigned long)&ones(8*ty->size); break;
+ case FLOAT: p->u.v.d = va_arg(ap, double); break;
+ case POINTER: p->u.v.p = va_arg(ap, void *); break;
+ default: assert(0);
+ }
+ va_end(ap);
+ return p;
+}
+
+Tree consttree(unsigned n, Type ty) {
+ if (isarray(ty))
+ ty = atop(ty);
+ else assert(isint(ty));
+ return cnsttree(ty, (unsigned long)n);
+}
+static Tree cmptree(int op, Tree l, Tree r) {
+ Type ty;
+
+ if (isarith(l->type) && isarith(r->type)) {
+ ty = binary(l->type, r->type);
+ l = cast(l, ty);
+ r = cast(r, ty);
+ } else if (compatible(l->type, r->type)) {
+ ty = unsignedptr;
+ l = cast(l, ty);
+ r = cast(r, ty);
+ } else {
+ ty = unsignedtype;
+ typeerror(op, l, r);
+ }
+ return simplify(mkop(op,ty), inttype, l, r);
+}
+static int compatible(Type ty1, Type ty2) {
+ return isptr(ty1) && !isfunc(ty1->type)
+ && isptr(ty2) && !isfunc(ty2->type)
+ && eqtype(unqual(ty1->type), unqual(ty2->type), 0);
+}
+int isnullptr(Tree e) {
+ Type ty = unqual(e->type);
+
+ return generic(e->op) == CNST
+ && ((ty->op == INT && e->u.v.i == 0)
+ || (ty->op == UNSIGNED && e->u.v.u == 0)
+ || (isvoidptr(ty) && e->u.v.p == NULL));
+}
+Tree eqtree(int op, Tree l, Tree r) {
+ Type xty = l->type, yty = r->type;
+
+ if ((isptr(xty) && isnullptr(r))
+ || (isptr(xty) && !isfunc(xty->type) && isvoidptr(yty))
+ || (isptr(xty) && isptr(yty)
+ && eqtype(unqual(xty->type), unqual(yty->type), 1))) {
+ Type ty = unsignedptr;
+ l = cast(l, ty);
+ r = cast(r, ty);
+ return simplify(mkop(op,ty), inttype, l, r);
+ }
+ if ((isptr(yty) && isnullptr(l))
+ || (isptr(yty) && !isfunc(yty->type) && isvoidptr(xty)))
+ return eqtree(op, r, l);
+ return cmptree(op, l, r);
+}
+
+Type assign(Type xty, Tree e) {
+ Type yty = unqual(e->type);
+
+ xty = unqual(xty);
+ if (isenum(xty))
+ xty = xty->type;
+ if (xty->size == 0 || yty->size == 0)
+ return NULL;
+ if ( (isarith(xty) && isarith(yty))
+ || (isstruct(xty) && xty == yty))
+ return xty;
+ if (isptr(xty) && isnullptr(e))
+ return xty;
+ if (((isvoidptr(xty) && isptr(yty))
+ || (isptr(xty) && isvoidptr(yty)))
+ && ( (isconst(xty->type) || !isconst(yty->type))
+ && (isvolatile(xty->type) || !isvolatile(yty->type))))
+ return xty;
+
+ if ((isptr(xty) && isptr(yty)
+ && eqtype(unqual(xty->type), unqual(yty->type), 1))
+ && ( (isconst(xty->type) || !isconst(yty->type))
+ && (isvolatile(xty->type) || !isvolatile(yty->type))))
+ return xty;
+ if (isptr(xty) && isptr(yty)
+ && ( (isconst(xty->type) || !isconst(yty->type))
+ && (isvolatile(xty->type) || !isvolatile(yty->type)))) {
+ Type lty = unqual(xty->type), rty = unqual(yty->type);
+ if ((isenum(lty) && rty == inttype)
+ || (isenum(rty) && lty == inttype)) {
+ if (Aflag >= 1)
+ warning("assignment between `%t' and `%t' is compiler-dependent\n",
+ xty, yty);
+ return xty;
+ }
+ }
+ return NULL;
+}
+Tree asgntree(int op, Tree l, Tree r) {
+ Type aty, ty;
+
+ r = pointer(r);
+ ty = assign(l->type, r);
+ if (ty)
+ r = cast(r, ty);
+ else {
+ typeerror(ASGN, l, r);
+ if (r->type == voidtype)
+ r = retype(r, inttype);
+ ty = r->type;
+ }
+ if (l->op != FIELD)
+ l = lvalue(l);
+ aty = l->type;
+ if (isptr(aty))
+ aty = unqual(aty)->type;
+ if ( isconst(aty)
+ || (isstruct(aty) && unqual(aty)->u.sym->u.s.cfields)) {
+ if (isaddrop(l->op)
+ && !l->u.sym->computed && !l->u.sym->generated)
+ error("assignment to const identifier `%s'\n",
+ l->u.sym->name);
+ else
+ error("assignment to const location\n");
+ }
+ if (l->op == FIELD) {
+ long n = 8*l->u.field->type->size - fieldsize(l->u.field);
+ if (n > 0 && isunsigned(l->u.field->type))
+ r = bittree(BAND, r,
+ cnsttree(r->type, (unsigned long)fieldmask(l->u.field)));
+ else if (n > 0) {
+ if (r->op == CNST+I) {
+ n = r->u.v.i;
+ if (n&(1<<(fieldsize(l->u.field)-1)))
+ n |= ~0UL<<fieldsize(l->u.field);
+ r = cnsttree(r->type, n);
+ } else
+ r = shtree(RSH,
+ shtree(LSH, r, cnsttree(inttype, n)),
+ cnsttree(inttype, n));
+ }
+ }
+ if (isstruct(ty) && isaddrop(l->op) && iscallb(r))
+ return tree(RIGHT, ty,
+ tree(CALL+B, ty, r->kids[0]->kids[0], l),
+ idtree(l->u.sym));
+ return tree(mkop(op,ty), ty, l, r);
+}
+Tree condtree(Tree e, Tree l, Tree r) {
+ Symbol t1;
+ Type ty, xty = l->type, yty = r->type;
+ Tree p;
+
+ if (isarith(xty) && isarith(yty))
+ ty = binary(xty, yty);
+ else if (eqtype(xty, yty, 1))
+ ty = unqual(xty);
+ else if (isptr(xty) && isnullptr(r))
+ ty = xty;
+ else if (isnullptr(l) && isptr(yty))
+ ty = yty;
+ else if ((isptr(xty) && !isfunc(xty->type) && isvoidptr(yty))
+ || (isptr(yty) && !isfunc(yty->type) && isvoidptr(xty)))
+ ty = voidptype;
+ else if ((isptr(xty) && isptr(yty)
+ && eqtype(unqual(xty->type), unqual(yty->type), 1)))
+ ty = xty;
+ else {
+ typeerror(COND, l, r);
+ return consttree(0, inttype);
+ }
+ if (isptr(ty)) {
+ ty = unqual(unqual(ty)->type);
+ if ((isptr(xty) && isconst(unqual(xty)->type))
+ || (isptr(yty) && isconst(unqual(yty)->type)))
+ ty = qual(CONST, ty);
+ if ((isptr(xty) && isvolatile(unqual(xty)->type))
+ || (isptr(yty) && isvolatile(unqual(yty)->type)))
+ ty = qual(VOLATILE, ty);
+ ty = ptr(ty);
+ }
+ switch (e->op) {
+ case CNST+I: return cast(e->u.v.i != 0 ? l : r, ty);
+ case CNST+U: return cast(e->u.v.u != 0 ? l : r, ty);
+ case CNST+P: return cast(e->u.v.p != 0 ? l : r, ty);
+ case CNST+F: return cast(e->u.v.d != 0.0 ? l : r, ty);
+ }
+ if (ty != voidtype && ty->size > 0) {
+ t1 = genident(REGISTER, unqual(ty), level);
+ /* t1 = temporary(REGISTER, unqual(ty)); */
+ l = asgn(t1, l);
+ r = asgn(t1, r);
+ } else
+ t1 = NULL;
+ p = tree(COND, ty, cond(e),
+ tree(RIGHT, ty, root(l), root(r)));
+ p->u.sym = t1;
+ return p;
+}
+/* addrof - address of p */
+Tree addrof(Tree p) {
+ Tree q = p;
+
+ for (;;)
+ switch (generic(q->op)) {
+ case RIGHT:
+ assert(q->kids[0] || q->kids[1]);
+ q = q->kids[1] ? q->kids[1] : q->kids[0];
+ continue;
+ case ASGN:
+ q = q->kids[1];
+ continue;
+ case COND: {
+ Symbol t1 = q->u.sym;
+ q->u.sym = 0;
+ q = idtree(t1);
+ /* fall thru */
+ }
+ case INDIR:
+ if (p == q)
+ return q->kids[0];
+ q = q->kids[0];
+ return tree(RIGHT, q->type, root(p), q);
+ default:
+ error("addressable object required\n");
+ return value(p);
+ }
+}
+
+/* andtree - construct tree for l [&& ||] r */
+static Tree andtree(int op, Tree l, Tree r) {
+ if (!isscalar(l->type) || !isscalar(r->type))
+ typeerror(op, l, r);
+ return simplify(op, inttype, cond(l), cond(r));
+}
+
+/* asgn - generate tree for assignment of expr e to symbol p sans qualifiers */
+Tree asgn(Symbol p, Tree e) {
+ if (isarray(p->type))
+ e = tree(ASGN+B, p->type, idtree(p),
+ tree(INDIR+B, e->type, e, NULL));
+ else {
+ Type ty = p->type;
+ p->type = unqual(p->type);
+ if (isstruct(p->type) && p->type->u.sym->u.s.cfields) {
+ p->type->u.sym->u.s.cfields = 0;
+ e = asgntree(ASGN, idtree(p), e);
+ p->type->u.sym->u.s.cfields = 1;
+ } else
+ e = asgntree(ASGN, idtree(p), e);
+ p->type = ty;
+ }
+ return e;
+}
+
+/* bittree - construct tree for l [& | ^ %] r */
+Tree bittree(int op, Tree l, Tree r) {
+ Type ty = inttype;
+
+ if (isint(l->type) && isint(r->type)) {
+ ty = binary(l->type, r->type);
+ l = cast(l, ty);
+ r = cast(r, ty);
+ } else
+ typeerror(op, l, r);
+ return simplify(op, ty, l, r);
+}
+
+/* multree - construct tree for l [* /] r */
+static Tree multree(int op, Tree l, Tree r) {
+ Type ty = inttype;
+
+ if (isarith(l->type) && isarith(r->type)) {
+ ty = binary(l->type, r->type);
+ l = cast(l, ty);
+ r = cast(r, ty);
+ } else
+ typeerror(op, l, r);
+ return simplify(op, ty, l, r);
+}
+
+/* shtree - construct tree for l [>> <<] r */
+Tree shtree(int op, Tree l, Tree r) {
+ Type ty = inttype;
+
+ if (isint(l->type) && isint(r->type)) {
+ ty = promote(l->type);
+ l = cast(l, ty);
+ r = cast(r, inttype);
+ } else
+ typeerror(op, l, r);
+ return simplify(op, ty, l, r);
+}
+
+/* subtree - construct tree for l - r */
+static Tree subtree(int op, Tree l, Tree r) {
+ long n;
+ Type ty = inttype;
+
+ if (isarith(l->type) && isarith(r->type)) {
+ ty = binary(l->type, r->type);
+ l = cast(l, ty);
+ r = cast(r, ty);
+ } else if (isptr(l->type) && !isfunc(l->type->type) && isint(r->type)) {
+ ty = unqual(l->type);
+ n = unqual(ty->type)->size;
+ if (n == 0)
+ error("unknown size for type `%t'\n", ty->type);
+ r = cast(r, promote(r->type));
+ if (n > 1)
+ r = multree(MUL, cnsttree(signedptr, n), r);
+ if (isunsigned(r->type))
+ r = cast(r, unsignedptr);
+ else
+ r = cast(r, signedptr);
+ return simplify(SUB+P, ty, l, r);
+ } else if (compatible(l->type, r->type)) {
+ ty = unqual(l->type);
+ n = unqual(ty->type)->size;
+ if (n == 0)
+ error("unknown size for type `%t'\n", ty->type);
+ l = simplify(SUB+U, unsignedptr,
+ cast(l, unsignedptr), cast(r, unsignedptr));
+ return simplify(DIV+I, longtype,
+ cast(l, longtype), cnsttree(longtype, n));
+ } else
+ typeerror(op, l, r);
+ return simplify(op, ty, l, r);
+}
+
+/* typeerror - issue "operands of op have illegal types `l' and `r'" */
+void typeerror(int op, Tree l, Tree r) {
+ int i;
+ static struct { int op; char *name; } ops[] = {
+ {ASGN, "="}, {INDIR, "*"}, {NEG, "-"},
+ {ADD, "+"}, {SUB, "-"}, {LSH, "<<"},
+ {MOD, "%"}, {RSH, ">>"}, {BAND, "&"},
+ {BCOM, "~"}, {BOR, "|"}, {BXOR, "^"},
+ {DIV, "/"}, {MUL, "*"}, {EQ, "=="},
+ {GE, ">="}, {GT, ">"}, {LE, "<="},
+ {LT, "<"}, {NE, "!="}, {AND, "&&"},
+ {NOT, "!"}, {OR, "||"}, {COND, "?:"},
+ {0, 0}
+ };
+
+ op = generic(op);
+ for (i = 0; ops[i].op; i++)
+ if (op == ops[i].op)
+ break;
+ assert(ops[i].name);
+ if (r)
+ error("operands of %s have illegal types `%t' and `%t'\n",
+ ops[i].name, l->type, r->type);
+ else
+ error("operand of unary %s has illegal type `%t'\n", ops[i].name,
+ l->type);
+}
diff --git a/src/tools/lcc/src/error.c b/src/tools/lcc/src/error.c
new file mode 100644
index 0000000..2187c10
--- /dev/null
+++ b/src/tools/lcc/src/error.c
@@ -0,0 +1,137 @@
+#include "c.h"
+
+
+static void printtoken(void);
+int errcnt = 0;
+int errlimit = 20;
+char kind[] = {
+#define xx(a,b,c,d,e,f,g) f,
+#define yy(a,b,c,d,e,f,g) f,
+#include "token.h"
+};
+int wflag; /* != 0 to suppress warning messages */
+
+void test(int tok, char set[]) {
+ if (t == tok)
+ t = gettok();
+ else {
+ expect(tok);
+ skipto(tok, set);
+ if (t == tok)
+ t = gettok();
+ }
+}
+void expect(int tok) {
+ if (t == tok)
+ t = gettok();
+ else {
+ error("syntax error; found");
+ printtoken();
+ fprint(stderr, " expecting `%k'\n", tok);
+ }
+}
+void error(const char *fmt, ...) {
+ va_list ap;
+
+ if (errcnt++ >= errlimit) {
+ errcnt = -1;
+ error("too many errors\n");
+ exit(1);
+ }
+ va_start(ap, fmt);
+ if (firstfile != file && firstfile && *firstfile)
+ fprint(stderr, "%s: ", firstfile);
+ fprint(stderr, "%w: ", &src);
+ vfprint(stderr, NULL, fmt, ap);
+ va_end(ap);
+}
+
+void skipto(int tok, char set[]) {
+ int n;
+ char *s;
+
+ assert(set);
+ for (n = 0; t != EOI && t != tok; t = gettok()) {
+ for (s = set; *s && kind[t] != *s; s++)
+ ;
+ if (kind[t] == *s)
+ break;
+ if (n++ == 0)
+ error("skipping");
+ if (n <= 8)
+ printtoken();
+ else if (n == 9)
+ fprint(stderr, " ...");
+ }
+ if (n > 8) {
+ fprint(stderr, " up to");
+ printtoken();
+ }
+ if (n > 0)
+ fprint(stderr, "\n");
+}
+/* fatal - issue fatal error message and exit */
+int fatal(const char *name, const char *fmt, int n) {
+ print("\n");
+ errcnt = -1;
+ error("compiler error in %s--", name);
+ fprint(stderr, fmt, n);
+ exit(EXIT_FAILURE);
+ return 0;
+}
+
+/* printtoken - print current token preceeded by a space */
+static void printtoken(void) {
+ switch (t) {
+ case ID: fprint(stderr, " `%s'", token); break;
+ case ICON:
+ fprint(stderr, " `%s'", vtoa(tsym->type, tsym->u.c.v));
+ break;
+ case SCON: {
+ int i, n;
+ if (ischar(tsym->type->type)) {
+ char *s = tsym->u.c.v.p;
+ n = tsym->type->size;
+ fprint(stderr, " \"");
+ for (i = 0; i < 20 && i < n && *s; s++, i++)
+ if (*s < ' ' || *s >= 0177)
+ fprint(stderr, "\\%o", *s);
+ else
+ fprint(stderr, "%c", *s);
+ } else { /* wchar_t string */
+ unsigned int *s = tsym->u.c.v.p;
+ assert(tsym->type->type->size == widechar->size);
+ n = tsym->type->size/widechar->size;
+ fprint(stderr, " L\"");
+ for (i = 0; i < 20 && i < n && *s; s++, i++)
+ if (*s < ' ' || *s >= 0177)
+ fprint(stderr, "\\x%x", *s);
+ else
+ fprint(stderr, "%c", *s);
+ }
+ if (i < n)
+ fprint(stderr, " ...");
+ else
+ fprint(stderr, "\"");
+ break;
+ }
+ case FCON:
+ fprint(stderr, " `%S'", token, (char*)cp - token);
+ break;
+ case '`': case '\'': fprint(stderr, " \"%k\"", t); break;
+ default: fprint(stderr, " `%k'", t);
+ }
+}
+
+/* warning - issue warning error message */
+void warning(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (wflag == 0) {
+ errcnt--;
+ error("warning: ");
+ vfprint(stderr, NULL, fmt, ap);
+ }
+ va_end(ap);
+}
diff --git a/src/tools/lcc/src/event.c b/src/tools/lcc/src/event.c
new file mode 100644
index 0000000..4549e3f
--- /dev/null
+++ b/src/tools/lcc/src/event.c
@@ -0,0 +1,28 @@
+#include "c.h"
+
+
+struct entry {
+ Apply func;
+ void *cl;
+};
+
+Events events;
+void attach(Apply func, void *cl, List *list) {
+ struct entry *p;
+
+ NEW(p, PERM);
+ p->func = func;
+ p->cl = cl;
+ *list = append(p, *list);
+}
+void apply(List event, void *arg1, void *arg2) {
+ if (event) {
+ List lp = event;
+ do {
+ struct entry *p = lp->x;
+ (*p->func)(p->cl, arg1, arg2);
+ lp = lp->link;
+ } while (lp != event);
+ }
+}
+
diff --git a/src/tools/lcc/src/expr.c b/src/tools/lcc/src/expr.c
new file mode 100644
index 0000000..96eec21
--- /dev/null
+++ b/src/tools/lcc/src/expr.c
@@ -0,0 +1,711 @@
+#include "c.h"
+
+
+static char prec[] = {
+#define xx(a,b,c,d,e,f,g) c,
+#define yy(a,b,c,d,e,f,g) c,
+#include "token.h"
+};
+static int oper[] = {
+#define xx(a,b,c,d,e,f,g) d,
+#define yy(a,b,c,d,e,f,g) d,
+#include "token.h"
+};
+float refinc = 1.0;
+static Tree expr2(void);
+static Tree expr3(int);
+static Tree nullcheck(Tree);
+static Tree postfix(Tree);
+static Tree unary(void);
+static Tree primary(void);
+static Type super(Type ty);
+
+static Type super(Type ty) {
+ switch (ty->op) {
+ case INT:
+ if (ty->size < inttype->size)
+ return inttype;
+ break;
+ case UNSIGNED:
+ if (ty->size < unsignedtype->size)
+ return unsignedtype;
+ break;
+ case POINTER:
+ return unsignedptr;
+ }
+ return ty;
+}
+Tree expr(int tok) {
+ static char stop[] = { IF, ID, '}', 0 };
+ Tree p = expr1(0);
+
+ while (t == ',') {
+ Tree q;
+ t = gettok();
+ q = pointer(expr1(0));
+ p = tree(RIGHT, q->type, root(value(p)), q);
+ }
+ if (tok)
+ test(tok, stop);
+ return p;
+}
+Tree expr0(int tok) {
+ return root(expr(tok));
+}
+Tree expr1(int tok) {
+ static char stop[] = { IF, ID, 0 };
+ Tree p = expr2();
+
+ if (t == '='
+ || (prec[t] >= 6 && prec[t] <= 8)
+ || (prec[t] >= 11 && prec[t] <= 13)) {
+ int op = t;
+ t = gettok();
+ if (oper[op] == ASGN)
+ p = asgntree(ASGN, p, value(expr1(0)));
+ else
+ {
+ expect('=');
+ p = incr(op, p, expr1(0));
+ }
+ }
+ if (tok)
+ test(tok, stop);
+ return p;
+}
+Tree incr(int op, Tree v, Tree e) {
+ return asgntree(ASGN, v, (*optree[op])(oper[op], v, e));
+}
+static Tree expr2(void) {
+ Tree p = expr3(4);
+
+ if (t == '?') {
+ Tree l, r;
+ Coordinate pts[2];
+ if (Aflag > 1 && isfunc(p->type))
+ warning("%s used in a conditional expression\n",
+ funcname(p));
+ p = pointer(p);
+ t = gettok();
+ pts[0] = src;
+ l = pointer(expr(':'));
+ pts[1] = src;
+ r = pointer(expr2());
+ if (events.points)
+ {
+ apply(events.points, &pts[0], &l);
+ apply(events.points, &pts[1], &r);
+ }
+ p = condtree(p, l, r);
+ }
+ return p;
+}
+Tree value(Tree p) {
+ int op = generic(rightkid(p)->op);
+
+ if (p->type != voidtype
+ && (op==AND || op==OR || op==NOT || op==EQ || op==NE
+ || op== LE || op==LT || op== GE || op==GT))
+ p = condtree(p, consttree(1, inttype),
+ consttree(0, inttype));
+ return p;
+}
+static Tree expr3(int k) {
+ int k1;
+ Tree p = unary();
+
+ for (k1 = prec[t]; k1 >= k; k1--)
+ while (prec[t] == k1 && *cp != '=') {
+ Tree r;
+ Coordinate pt;
+ int op = t;
+ t = gettok();
+ pt = src;
+ p = pointer(p);
+ if (op == ANDAND || op == OROR) {
+ r = pointer(expr3(k1));
+ if (events.points)
+ apply(events.points, &pt, &r);
+ } else
+ r = pointer(expr3(k1 + 1));
+ p = (*optree[op])(oper[op], p, r);
+ }
+ return p;
+}
+static Tree unary(void) {
+ Tree p;
+
+ switch (t) {
+ case '*': t = gettok(); p = unary(); p = pointer(p);
+ if (isptr(p->type)
+ && (isfunc(p->type->type) || isarray(p->type->type)))
+ p = retype(p, p->type->type);
+ else {
+ if (YYnull)
+ p = nullcheck(p);
+ p = rvalue(p);
+ } break;
+ case '&': t = gettok(); p = unary(); if (isarray(p->type) || isfunc(p->type))
+ p = retype(p, ptr(p->type));
+ else
+ p = lvalue(p);
+ if (isaddrop(p->op) && p->u.sym->sclass == REGISTER)
+ error("invalid operand of unary &; `%s' is declared register\n", p->u.sym->name);
+
+ else if (isaddrop(p->op))
+ p->u.sym->addressed = 1;
+ break;
+ case '+': t = gettok(); p = unary(); p = pointer(p);
+ if (isarith(p->type))
+ p = cast(p, promote(p->type));
+ else
+ typeerror(ADD, p, NULL); break;
+ case '-': t = gettok(); p = unary(); p = pointer(p);
+ if (isarith(p->type)) {
+ Type ty = promote(p->type);
+ p = cast(p, ty);
+ if (isunsigned(ty)) {
+ warning("unsigned operand of unary -\n");
+ p = simplify(ADD, ty, simplify(BCOM, ty, p, NULL), cnsttree(ty, 1UL));
+ } else
+ p = simplify(NEG, ty, p, NULL);
+ } else
+ typeerror(SUB, p, NULL); break;
+ case '~': t = gettok(); p = unary(); p = pointer(p);
+ if (isint(p->type)) {
+ Type ty = promote(p->type);
+ p = simplify(BCOM, ty, cast(p, ty), NULL);
+ } else
+ typeerror(BCOM, p, NULL); break;
+ case '!': t = gettok(); p = unary(); p = pointer(p);
+ if (isscalar(p->type))
+ p = simplify(NOT, inttype, cond(p), NULL);
+ else
+ typeerror(NOT, p, NULL); break;
+ case INCR: t = gettok(); p = unary(); p = incr(INCR, pointer(p), consttree(1, inttype)); break;
+ case DECR: t = gettok(); p = unary(); p = incr(DECR, pointer(p), consttree(1, inttype)); break;
+ case TYPECODE: case SIZEOF: { int op = t;
+ Type ty;
+ p = NULL;
+ t = gettok();
+ if (t == '(') {
+ t = gettok();
+ if (istypename(t, tsym)) {
+ ty = typename();
+ expect(')');
+ } else {
+ p = postfix(expr(')'));
+ ty = p->type;
+ }
+ } else {
+ p = unary();
+ ty = p->type;
+ }
+ assert(ty);
+ if (op == TYPECODE)
+ p = cnsttree(inttype, (long)ty->op);
+ else {
+ if (isfunc(ty) || ty->size == 0)
+ error("invalid type argument `%t' to `sizeof'\n", ty);
+ else if (p && rightkid(p)->op == FIELD)
+ error("`sizeof' applied to a bit field\n");
+ p = cnsttree(unsignedlong, (unsigned long)ty->size);
+ } } break;
+ case '(':
+ t = gettok();
+ if (istypename(t, tsym)) {
+ Type ty, ty1 = typename(), pty;
+ expect(')');
+ ty = unqual(ty1);
+ if (isenum(ty)) {
+ Type ty2 = ty->type;
+ if (isconst(ty1))
+ ty2 = qual(CONST, ty2);
+ if (isvolatile(ty1))
+ ty2 = qual(VOLATILE, ty2);
+ ty1 = ty2;
+ ty = ty->type;
+ }
+ p = pointer(unary());
+ pty = p->type;
+ if (isenum(pty))
+ pty = pty->type;
+ if ((isarith(pty) && isarith(ty))
+ || (isptr(pty) && isptr(ty))) {
+ explicitCast++;
+ p = cast(p, ty);
+ explicitCast--;
+ } else if ((isptr(pty) && isint(ty))
+ || (isint(pty) && isptr(ty))) {
+ if (Aflag >= 1 && ty->size < pty->size)
+ warning("conversion from `%t' to `%t' is compiler dependent\n", p->type, ty);
+
+ p = cast(p, ty);
+ } else if (ty != voidtype) {
+ error("cast from `%t' to `%t' is illegal\n",
+ p->type, ty1);
+ ty1 = inttype;
+ }
+ if (generic(p->op) == INDIR || ty->size == 0)
+ p = tree(RIGHT, ty1, NULL, p);
+ else
+ p = retype(p, ty1);
+ } else
+ p = postfix(expr(')'));
+ break;
+ default:
+ p = postfix(primary());
+ }
+ return p;
+}
+
+static Tree postfix(Tree p) {
+ for (;;)
+ switch (t) {
+ case INCR: p = tree(RIGHT, p->type,
+ tree(RIGHT, p->type,
+ p,
+ incr(t, p, consttree(1, inttype))),
+ p);
+ t = gettok(); break;
+ case DECR: p = tree(RIGHT, p->type,
+ tree(RIGHT, p->type,
+ p,
+ incr(t, p, consttree(1, inttype))),
+ p);
+ t = gettok(); break;
+ case '[': {
+ Tree q;
+ t = gettok();
+ q = expr(']');
+ if (YYnull) {
+ if (isptr(p->type))
+ p = nullcheck(p);
+ else if (isptr(q->type))
+ q = nullcheck(q);
+ }
+ p = (*optree['+'])(ADD, pointer(p), pointer(q));
+ if (isptr(p->type) && isarray(p->type->type))
+ p = retype(p, p->type->type);
+ else
+ p = rvalue(p);
+ } break;
+ case '(': {
+ Type ty;
+ Coordinate pt;
+ p = pointer(p);
+ if (isptr(p->type) && isfunc(p->type->type))
+ ty = p->type->type;
+ else {
+ error("found `%t' expected a function\n", p->type);
+ ty = func(voidtype, NULL, 1);
+ p = retype(p, ptr(ty));
+ }
+ pt = src;
+ t = gettok();
+ p = call(p, ty, pt);
+ } break;
+ case '.': t = gettok();
+ if (t == ID) {
+ if (isstruct(p->type)) {
+ Tree q = addrof(p);
+ p = field(q, token);
+ q = rightkid(q);
+ if (isaddrop(q->op) && q->u.sym->temporary)
+ p = tree(RIGHT, p->type, p, NULL);
+ } else
+ error("left operand of . has incompatible type `%t'\n",
+ p->type);
+ t = gettok();
+ } else
+ error("field name expected\n"); break;
+ case DEREF: t = gettok();
+ p = pointer(p);
+ if (t == ID) {
+ if (isptr(p->type) && isstruct(p->type->type)) {
+ if (YYnull)
+ p = nullcheck(p);
+ p = field(p, token);
+ } else
+ error("left operand of -> has incompatible type `%t'\n", p->type);
+
+ t = gettok();
+ } else
+ error("field name expected\n"); break;
+ default:
+ return p;
+ }
+}
+static Tree primary(void) {
+ Tree p;
+
+ assert(t != '(');
+ switch (t) {
+ case ICON:
+ case FCON: p = tree(mkop(CNST,tsym->type), tsym->type, NULL, NULL);
+ p->u.v = tsym->u.c.v;
+ break;
+ case SCON: if (ischar(tsym->type->type))
+ tsym->u.c.v.p = stringn(tsym->u.c.v.p, tsym->type->size);
+ else
+ tsym->u.c.v.p = memcpy(allocate(tsym->type->size, PERM), tsym->u.c.v.p, tsym->type->size);
+ tsym = constant(tsym->type, tsym->u.c.v);
+ if (tsym->u.c.loc == NULL)
+ tsym->u.c.loc = genident(STATIC, tsym->type, GLOBAL);
+ p = idtree(tsym->u.c.loc); break;
+ case ID: if (tsym == NULL)
+ {
+ Symbol p = install(token, &identifiers, level, FUNC);
+ p->src = src;
+ if (getchr() == '(') {
+ Symbol q = lookup(token, externals);
+ p->type = func(inttype, NULL, 1);
+ p->sclass = EXTERN;
+ if (Aflag >= 1)
+ warning("missing prototype\n");
+ if (q && !eqtype(q->type, p->type, 1))
+ warning("implicit declaration of `%s' does not match previous declaration at %w\n", q->name, &q->src);
+
+ if (q == NULL) {
+ q = install(p->name, &externals, GLOBAL, PERM);
+ q->type = p->type;
+ q->sclass = EXTERN;
+ q->src = src;
+ (*IR->defsymbol)(q);
+ }
+ p->u.alias = q;
+ } else {
+ error("undeclared identifier `%s'\n", p->name);
+ p->sclass = AUTO;
+ p->type = inttype;
+ if (p->scope == GLOBAL)
+ (*IR->defsymbol)(p);
+ else
+ addlocal(p);
+ }
+ t = gettok();
+ if (xref)
+ use(p, src);
+ return idtree(p);
+ }
+ if (xref)
+ use(tsym, src);
+ if (tsym->sclass == ENUM)
+ p = consttree(tsym->u.value, inttype);
+ else {
+ if (tsym->sclass == TYPEDEF)
+ error("illegal use of type name `%s'\n", tsym->name);
+ p = idtree(tsym);
+ } break;
+ case FIRSTARG:
+ if (level > PARAM && cfunc && cfunc->u.f.callee[0])
+ p = idtree(cfunc->u.f.callee[0]);
+ else {
+ error("illegal use of `%k'\n", FIRSTARG);
+ p = cnsttree(inttype, 0L);
+ }
+ break;
+ default:
+ error("illegal expression\n");
+ p = cnsttree(inttype, 0L);
+ }
+ t = gettok();
+ return p;
+}
+Tree idtree(Symbol p) {
+ int op;
+ Tree e;
+ Type ty = p->type ? unqual(p->type) : voidptype;
+
+ if (p->scope == GLOBAL || p->sclass == STATIC)
+ op = ADDRG;
+ else if (p->scope == PARAM) {
+ op = ADDRF;
+ if (isstruct(p->type) && !IR->wants_argb)
+ {
+ e = tree(mkop(op,voidptype), ptr(ptr(p->type)), NULL, NULL);
+ e->u.sym = p;
+ return rvalue(rvalue(e));
+ }
+ } else if (p->sclass == EXTERN) {
+ assert(p->u.alias);
+ p = p->u.alias;
+ op = ADDRG;
+ } else
+ op = ADDRL;
+ p->ref += refinc;
+ if (isarray(ty))
+ e = tree(mkop(op,voidptype), p->type, NULL, NULL);
+ else if (isfunc(ty))
+ e = tree(mkop(op,funcptype), p->type, NULL, NULL);
+ else
+ e = tree(mkop(op,voidptype), ptr(p->type), NULL, NULL);
+ e->u.sym = p;
+ if (isptr(e->type))
+ e = rvalue(e);
+ return e;
+}
+
+Tree rvalue(Tree p) {
+ Type ty = deref(p->type);
+
+ ty = unqual(ty);
+ return tree(mkop(INDIR,ty), ty, p, NULL);
+}
+Tree lvalue(Tree p) {
+ if (generic(p->op) != INDIR) {
+ error("lvalue required\n");
+ return value(p);
+ } else if (unqual(p->type) == voidtype)
+ warning("`%t' used as an lvalue\n", p->type);
+ return p->kids[0];
+}
+Tree retype(Tree p, Type ty) {
+ Tree q;
+
+ if (p->type == ty)
+ return p;
+ q = tree(p->op, ty, p->kids[0], p->kids[1]);
+ q->node = p->node;
+ q->u = p->u;
+ return q;
+}
+Tree rightkid(Tree p) {
+ while (p && p->op == RIGHT)
+ if (p->kids[1])
+ p = p->kids[1];
+ else if (p->kids[0])
+ p = p->kids[0];
+ else
+ assert(0);
+ assert(p);
+ return p;
+}
+int hascall(Tree p) {
+ if (p == 0)
+ return 0;
+ if (generic(p->op) == CALL || (IR->mulops_calls &&
+ (p->op == DIV+I || p->op == MOD+I || p->op == MUL+I
+ || p->op == DIV+U || p->op == MOD+U || p->op == MUL+U)))
+ return 1;
+ return hascall(p->kids[0]) || hascall(p->kids[1]);
+}
+Type binary(Type xty, Type yty) {
+#define xx(t) if (xty == t || yty == t) return t
+ xx(longdouble);
+ xx(doubletype);
+ xx(floattype);
+ xx(unsignedlonglong);
+ xx(longlong);
+ xx(unsignedlong);
+ if ((xty == longtype && yty == unsignedtype)
+ || (xty == unsignedtype && yty == longtype)) {
+ if (longtype->size > unsignedtype->size)
+ return longtype;
+ else
+ return unsignedlong;
+ }
+ xx(longtype);
+ xx(unsignedtype);
+ return inttype;
+#undef xx
+}
+Tree pointer(Tree p) {
+ if (isarray(p->type))
+ /* assert(p->op != RIGHT || p->u.sym == NULL), */
+ p = retype(p, atop(p->type));
+ else if (isfunc(p->type))
+ p = retype(p, ptr(p->type));
+ return p;
+}
+Tree cond(Tree p) {
+ int op = generic(rightkid(p)->op);
+
+ if (op == AND || op == OR || op == NOT
+ || op == EQ || op == NE
+ || op == LE || op == LT || op == GE || op == GT)
+ return p;
+ p = pointer(p);
+ return (*optree[NEQ])(NE, p, consttree(0, inttype));
+}
+Tree cast(Tree p, Type type) {
+ Type src, dst;
+
+ p = value(p);
+ if (p->type == type)
+ return p;
+ dst = unqual(type);
+ src = unqual(p->type);
+ if (src->op != dst->op || src->size != dst->size) {
+ switch (src->op) {
+ case INT:
+ if (src->size < inttype->size)
+ p = simplify(CVI, inttype, p, NULL);
+ break;
+ case UNSIGNED:
+ if (src->size < inttype->size)
+ p = simplify(CVU, inttype, p, NULL);
+ else if (src->size < unsignedtype->size)
+ p = simplify(CVU, unsignedtype, p, NULL);
+ break;
+ case ENUM:
+ p = retype(p, inttype);
+ break;
+ case POINTER:
+ if (isint(dst) && src->size > dst->size)
+ warning("conversion from `%t' to `%t' is undefined\n", p->type, type);
+ p = simplify(CVP, super(src), p, NULL);
+ break;
+ case FLOAT:
+ break;
+ default: assert(0);
+ }
+ {
+ src = unqual(p->type);
+ dst = super(dst);
+ if (src->op != dst->op)
+ switch (src->op) {
+ case INT:
+ p = simplify(CVI, dst, p, NULL);
+ break;
+ case UNSIGNED:
+ if (isfloat(dst)) {
+ Type ssrc = signedint(src);
+ Tree two = cnsttree(longdouble, (double)2.0);
+ p = (*optree['+'])(ADD,
+ (*optree['*'])(MUL,
+ two,
+ simplify(CVU, ssrc,
+ simplify(RSH, src,
+ p, consttree(1, inttype)), NULL)),
+ simplify(CVU, ssrc,
+ simplify(BAND, src,
+ p, consttree(1, unsignedtype)), NULL));
+ } else
+ p = simplify(CVU, dst, p, NULL);
+ break;
+ case FLOAT:
+ if (isunsigned(dst)) {
+ Type sdst = signedint(dst);
+ Tree c = cast(cnsttree(longdouble, (double)sdst->u.sym->u.limits.max.i + 1), src);
+ p = condtree(
+ simplify(GE, src, p, c),
+ (*optree['+'])(ADD,
+ cast(cast(simplify(SUB, src, p, c), sdst), dst),
+ cast(cnsttree(unsignedlong, (unsigned long)sdst->u.sym->u.limits.max.i + 1), dst)),
+ simplify(CVF, sdst, p, NULL));
+ } else
+ p = simplify(CVF, dst, p, NULL);
+ break;
+ default: assert(0);
+ }
+ dst = unqual(type);
+ }
+ }
+ src = unqual(p->type);
+ switch (src->op) {
+ case INT:
+ if (src->op != dst->op || src->size != dst->size)
+ p = simplify(CVI, dst, p, NULL);
+ break;
+ case UNSIGNED:
+ if (src->op != dst->op || src->size != dst->size)
+ p = simplify(CVU, dst, p, NULL);
+ break;
+ case FLOAT:
+ if (src->op != dst->op || src->size != dst->size)
+ p = simplify(CVF, dst, p, NULL);
+ break;
+ case POINTER:
+ if (src->op != dst->op)
+ p = simplify(CVP, dst, p, NULL);
+ else {
+ if ((isfunc(src->type) && !isfunc(dst->type))
+ || (!isnullptr(p) && !isfunc(src->type) && isfunc(dst->type)))
+ warning("conversion from `%t' to `%t' is compiler dependent\n", p->type, type);
+
+ if (src->size != dst->size)
+ p = simplify(CVP, dst, p, NULL);
+ }
+ break;
+ default: assert(0);
+ }
+ return retype(p, type);
+}
+Tree field(Tree p, const char *name) {
+ Field q;
+ Type ty1, ty = p->type;
+
+ if (isptr(ty))
+ ty = deref(ty);
+ ty1 = ty;
+ ty = unqual(ty);
+ if ((q = fieldref(name, ty)) != NULL) {
+ if (isarray(q->type)) {
+ ty = q->type->type;
+ if (isconst(ty1) && !isconst(ty))
+ ty = qual(CONST, ty);
+ if (isvolatile(ty1) && !isvolatile(ty))
+ ty = qual(VOLATILE, ty);
+ ty = array(ty, q->type->size/ty->size, q->type->align);
+ } else {
+ ty = q->type;
+ if (isconst(ty1) && !isconst(ty))
+ ty = qual(CONST, ty);
+ if (isvolatile(ty1) && !isvolatile(ty))
+ ty = qual(VOLATILE, ty);
+ ty = ptr(ty);
+ }
+ if (YYcheck && !isaddrop(p->op) && q->offset > 0) /* omit */
+ p = nullcall(ty, YYcheck, p, consttree(q->offset, inttype)); /* omit */
+ else /* omit */
+ p = simplify(ADD+P, ty, p, consttree(q->offset, inttype));
+
+ if (q->lsb) {
+ p = tree(FIELD, ty->type, rvalue(p), NULL);
+ p->u.field = q;
+ } else if (!isarray(q->type))
+ p = rvalue(p);
+
+ } else {
+ error("unknown field `%s' of `%t'\n", name, ty);
+ p = rvalue(retype(p, ptr(inttype)));
+ }
+ return p;
+}
+/* funcname - return name of function f or a function' */
+char *funcname(Tree f) {
+ if (isaddrop(f->op))
+ return stringf("`%s'", f->u.sym->name);
+ return "a function";
+}
+static Tree nullcheck(Tree p) {
+ if (!needconst && YYnull && isptr(p->type)) {
+ p = value(p);
+ if (strcmp(YYnull->name, "_YYnull") == 0) {
+ Symbol t1 = temporary(REGISTER, voidptype);
+ p = tree(RIGHT, p->type,
+ tree(OR, voidtype,
+ cond(asgn(t1, cast(p, voidptype))),
+ vcall(YYnull, voidtype, (file && *file ? pointer(idtree(mkstr(file)->u.c.loc)) : cnsttree(voidptype, NULL)), cnsttree(inttype, (long)lineno) , NULL)),
+ idtree(t1));
+ }
+
+ else
+ p = nullcall(p->type, YYnull, p, cnsttree(inttype, 0L));
+
+ }
+ return p;
+}
+Tree nullcall(Type pty, Symbol f, Tree p, Tree e) {
+ Type ty;
+
+ if (isarray(pty))
+ return retype(nullcall(atop(pty), f, p, e), pty);
+ ty = unqual(unqual(p->type)->type);
+ return vcall(f, pty,
+ p, e,
+ cnsttree(inttype, (long)ty->size),
+ cnsttree(inttype, (long)ty->align),
+ (file && *file ? pointer(idtree(mkstr(file)->u.c.loc)) : cnsttree(voidptype, NULL)), cnsttree(inttype, (long)lineno) , NULL);
+}
diff --git a/src/tools/lcc/src/gen.c b/src/tools/lcc/src/gen.c
new file mode 100644
index 0000000..4ee170d
--- /dev/null
+++ b/src/tools/lcc/src/gen.c
@@ -0,0 +1,830 @@
+#include "c.h"
+
+
+#define readsreg(p) \
+ (generic((p)->op)==INDIR && (p)->kids[0]->op==VREG+P)
+#define setsrc(d) ((d) && (d)->x.regnode && \
+ (d)->x.regnode->set == src->x.regnode->set && \
+ (d)->x.regnode->mask&src->x.regnode->mask)
+
+#define relink(a, b) ((b)->x.prev = (a), (a)->x.next = (b))
+
+static Symbol askfixedreg(Symbol);
+static Symbol askreg(Symbol, unsigned*);
+static void blkunroll(int, int, int, int, int, int, int[]);
+static void docall(Node);
+static void dumpcover(Node, int, int);
+static void dumpregs(char *, char *, char *);
+static void dumprule(int);
+static void dumptree(Node);
+static unsigned emitasm(Node, int);
+static void genreload(Node, Symbol, int);
+static void genspill(Symbol, Node, Symbol);
+static Symbol getreg(Symbol, unsigned*, Node);
+static int getrule(Node, int);
+static void linearize(Node, Node);
+static int moveself(Node);
+static void prelabel(Node);
+static Node* prune(Node, Node*);
+static void putreg(Symbol);
+static void ralloc(Node);
+static void reduce(Node, int);
+static int reprune(Node*, int, int, Node);
+static int requate(Node);
+static Node reuse(Node, int);
+static void rewrite(Node);
+static Symbol spillee(Symbol, unsigned mask[], Node);
+static void spillr(Symbol, Node);
+static int uses(Node, Regnode);
+
+int offset;
+
+int maxoffset;
+
+int framesize;
+int argoffset;
+
+int maxargoffset;
+
+int dalign, salign;
+int bflag = 0; /* omit */
+int dflag = 0;
+
+int swap;
+
+unsigned (*emitter)(Node, int) = emitasm;
+static char NeedsReg[] = {
+ 0, /* unused */
+ 1, /* CNST */
+ 0, 0, /* ARG ASGN */
+ 1, /* INDIR */
+ 0, 0, 1, 1, /* - - CVF CVI */
+ 1, 0, 1, 1, /* CVP - CVU NEG */
+ 1, /* CALL */
+ 1, /* LOAD */
+ 0, /* RET */
+ 1, 1, 1, /* ADDRG ADDRF ADDRL */
+ 1, 1, 1, 1, 1, /* ADD SUB LSH MOD RSH */
+ 1, 1, 1, 1, /* BAND BCOM BOR BXOR */
+ 1, 1, /* DIV MUL */
+ 0, 0, 0, 0, 0, 0, /* EQ GE GT LE LT NE */
+ 0, 0 /* JUMP LABEL */
+};
+Node head;
+
+unsigned freemask[2];
+unsigned usedmask[2];
+unsigned tmask[2];
+unsigned vmask[2];
+Symbol mkreg(char *fmt, int n, int mask, int set) {
+ Symbol p;
+
+ NEW0(p, PERM);
+ p->name = p->x.name = stringf(fmt, n);
+ NEW0(p->x.regnode, PERM);
+ p->x.regnode->number = n;
+ p->x.regnode->mask = mask<<n;
+ p->x.regnode->set = set;
+ return p;
+}
+Symbol mkwildcard(Symbol *syms) {
+ Symbol p;
+
+ NEW0(p, PERM);
+ p->name = p->x.name = "wildcard";
+ p->x.wildcard = syms;
+ return p;
+}
+void mkauto(Symbol p) {
+ assert(p->sclass == AUTO);
+ offset = roundup(offset + p->type->size, p->type->align);
+ p->x.offset = -offset;
+ p->x.name = stringd(-offset);
+}
+void blockbeg(Env *e) {
+ e->offset = offset;
+ e->freemask[IREG] = freemask[IREG];
+ e->freemask[FREG] = freemask[FREG];
+}
+void blockend(Env *e) {
+ if (offset > maxoffset)
+ maxoffset = offset;
+ offset = e->offset;
+ freemask[IREG] = e->freemask[IREG];
+ freemask[FREG] = e->freemask[FREG];
+}
+int mkactual(int align, int size) {
+ int n = roundup(argoffset, align);
+
+ argoffset = n + size;
+ return n;
+}
+static void docall(Node p) {
+ p->syms[1] = p->syms[0];
+ p->syms[0] = intconst(argoffset);
+ if (argoffset > maxargoffset)
+ maxargoffset = argoffset;
+ argoffset = 0;
+}
+void blkcopy(int dreg, int doff, int sreg, int soff, int size, int tmp[]) {
+ assert(size >= 0);
+ if (size == 0)
+ return;
+ else if (size <= 2)
+ blkunroll(size, dreg, doff, sreg, soff, size, tmp);
+ else if (size == 3) {
+ blkunroll(2, dreg, doff, sreg, soff, 2, tmp);
+ blkunroll(1, dreg, doff+2, sreg, soff+2, 1, tmp);
+ }
+ else if (size <= 16) {
+ blkunroll(4, dreg, doff, sreg, soff, size&~3, tmp);
+ blkcopy(dreg, doff+(size&~3),
+ sreg, soff+(size&~3), size&3, tmp);
+ }
+ else
+ (*IR->x.blkloop)(dreg, doff, sreg, soff, size, tmp);
+}
+static void blkunroll(int k, int dreg, int doff, int sreg, int soff, int size, int tmp[]) {
+ int i;
+
+ assert(IR->x.max_unaligned_load);
+ if (k > IR->x.max_unaligned_load
+ && (k > salign || k > dalign))
+ k = IR->x.max_unaligned_load;
+ for (i = 0; i+k < size; i += 2*k) {
+ (*IR->x.blkfetch)(k, soff+i, sreg, tmp[0]);
+ (*IR->x.blkfetch)(k, soff+i+k, sreg, tmp[1]);
+ (*IR->x.blkstore)(k, doff+i, dreg, tmp[0]);
+ (*IR->x.blkstore)(k, doff+i+k, dreg, tmp[1]);
+ }
+ if (i < size) {
+ (*IR->x.blkfetch)(k, i+soff, sreg, tmp[0]);
+ (*IR->x.blkstore)(k, i+doff, dreg, tmp[0]);
+ }
+}
+void parseflags(int argc, char *argv[]) {
+ int i;
+
+ for (i = 0; i < argc; i++)
+ if (strcmp(argv[i], "-d") == 0)
+ dflag = 1;
+ else if (strcmp(argv[i], "-b") == 0) /* omit */
+ bflag = 1; /* omit */
+}
+static int getrule(Node p, int nt) {
+ int rulenum;
+
+ assert(p);
+ rulenum = (*IR->x._rule)(p->x.state, nt);
+ if (!rulenum) {
+ fprint(stderr, "(%x->op=%s at %w is corrupt.)\n", p, opname(p->op), &src);
+ assert(0);
+ }
+ return rulenum;
+}
+static void reduce(Node p, int nt) {
+ int rulenum, i;
+ short *nts;
+ Node kids[10];
+
+ p = reuse(p, nt);
+ rulenum = getrule(p, nt);
+ nts = IR->x._nts[rulenum];
+ (*IR->x._kids)(p, rulenum, kids);
+ for (i = 0; nts[i]; i++)
+ reduce(kids[i], nts[i]);
+ if (IR->x._isinstruction[rulenum]) {
+ assert(p->x.inst == 0 || p->x.inst == nt);
+ p->x.inst = nt;
+ if (p->syms[RX] && p->syms[RX]->temporary) {
+ debug(fprint(stderr, "(using %s)\n", p->syms[RX]->name));
+ p->syms[RX]->x.usecount++;
+ }
+ }
+}
+static Node reuse(Node p, int nt) {
+ struct _state {
+ short cost[1];
+ };
+ Symbol r = p->syms[RX];
+
+ if (generic(p->op) == INDIR && p->kids[0]->op == VREG+P
+ && r->u.t.cse && p->x.mayrecalc
+ && ((struct _state*)r->u.t.cse->x.state)->cost[nt] == 0)
+ return r->u.t.cse;
+ else
+ return p;
+}
+
+int mayrecalc(Node p) {
+ int op;
+
+ assert(p && p->syms[RX]);
+ if (p->syms[RX]->u.t.cse == NULL)
+ return 0;
+ op = generic(p->syms[RX]->u.t.cse->op);
+ if (op == CNST || op == ADDRF || op == ADDRG || op == ADDRL) {
+ p->x.mayrecalc = 1;
+ return 1;
+ } else
+ return 0;
+}
+static Node *prune(Node p, Node pp[]) {
+ if (p == NULL)
+ return pp;
+ p->x.kids[0] = p->x.kids[1] = p->x.kids[2] = NULL;
+ if (p->x.inst == 0)
+ return prune(p->kids[1], prune(p->kids[0], pp));
+ else if (p->syms[RX] && p->syms[RX]->temporary
+ && p->syms[RX]->x.usecount < 2) {
+ p->x.inst = 0;
+ debug(fprint(stderr, "(clobbering %s)\n", p->syms[RX]->name));
+ return prune(p->kids[1], prune(p->kids[0], pp));
+ }
+ else {
+ prune(p->kids[1], prune(p->kids[0], &p->x.kids[0]));
+ *pp = p;
+ return pp + 1;
+ }
+}
+
+#define ck(i) return (i) ? 0 : LBURG_MAX
+
+int range(Node p, int lo, int hi) {
+ Symbol s = p->syms[0];
+
+ switch (specific(p->op)) {
+ case ADDRF+P:
+ case ADDRL+P: ck(s->x.offset >= lo && s->x.offset <= hi);
+ case CNST+I: ck(s->u.c.v.i >= lo && s->u.c.v.i <= hi);
+ case CNST+U: ck(s->u.c.v.u >= lo && s->u.c.v.u <= hi);
+ case CNST+P: ck(s->u.c.v.p == 0 && lo <= 0 && hi >= 0);
+ }
+ return LBURG_MAX;
+}
+static void dumptree(Node p) {
+ if (p->op == VREG+P && p->syms[0]) {
+ fprint(stderr, "VREGP(%s)", p->syms[0]->name);
+ return;
+ } else if (generic(p->op) == LOAD) {
+ fprint(stderr, "LOAD(");
+ dumptree(p->kids[0]);
+ fprint(stderr, ")");
+ return;
+ }
+ fprint(stderr, "%s(", opname(p->op));
+ switch (generic(p->op)) {
+ case CNST: case LABEL:
+ case ADDRG: case ADDRF: case ADDRL:
+ if (p->syms[0])
+ fprint(stderr, "%s", p->syms[0]->name);
+ break;
+ case RET:
+ if (p->kids[0])
+ dumptree(p->kids[0]);
+ break;
+ case CVF: case CVI: case CVP: case CVU: case JUMP:
+ case ARG: case BCOM: case NEG: case INDIR:
+ dumptree(p->kids[0]);
+ break;
+ case CALL:
+ if (optype(p->op) != B) {
+ dumptree(p->kids[0]);
+ break;
+ }
+ /* else fall thru */
+ case EQ: case NE: case GT: case GE: case LE: case LT:
+ case ASGN: case BOR: case BAND: case BXOR: case RSH: case LSH:
+ case ADD: case SUB: case DIV: case MUL: case MOD:
+ dumptree(p->kids[0]);
+ fprint(stderr, ", ");
+ dumptree(p->kids[1]);
+ break;
+ default: assert(0);
+ }
+ fprint(stderr, ")");
+}
+static void dumpcover(Node p, int nt, int in) {
+ int rulenum, i;
+ short *nts;
+ Node kids[10];
+
+ p = reuse(p, nt);
+ rulenum = getrule(p, nt);
+ nts = IR->x._nts[rulenum];
+ fprint(stderr, "dumpcover(%x) = ", p);
+ for (i = 0; i < in; i++)
+ fprint(stderr, " ");
+ dumprule(rulenum);
+ (*IR->x._kids)(p, rulenum, kids);
+ for (i = 0; nts[i]; i++)
+ dumpcover(kids[i], nts[i], in+1);
+}
+
+static void dumprule(int rulenum) {
+ assert(rulenum);
+ fprint(stderr, "%s / %s", IR->x._string[rulenum],
+ IR->x._templates[rulenum]);
+ if (!IR->x._isinstruction[rulenum])
+ fprint(stderr, "\n");
+}
+static unsigned emitasm(Node p, int nt) {
+ int rulenum;
+ short *nts;
+ char *fmt;
+ Node kids[10];
+
+ p = reuse(p, nt);
+ rulenum = getrule(p, nt);
+ nts = IR->x._nts[rulenum];
+ fmt = IR->x._templates[rulenum];
+ assert(fmt);
+ if (IR->x._isinstruction[rulenum] && p->x.emitted)
+ print("%s", p->syms[RX]->x.name);
+ else if (*fmt == '#')
+ (*IR->x.emit2)(p);
+ else {
+ if (*fmt == '?') {
+ fmt++;
+ assert(p->kids[0]);
+ if (p->syms[RX] == p->x.kids[0]->syms[RX])
+ while (*fmt++ != '\n')
+ ;
+ }
+ for ((*IR->x._kids)(p, rulenum, kids); *fmt; fmt++)
+ if (*fmt != '%')
+ (void)putchar(*fmt);
+ else if (*++fmt == 'F')
+ print("%d", framesize);
+ else if (*fmt >= '0' && *fmt <= '9')
+ emitasm(kids[*fmt - '0'], nts[*fmt - '0']);
+ else if (*fmt >= 'a' && *fmt < 'a' + NELEMS(p->syms))
+ fputs(p->syms[*fmt - 'a']->x.name, stdout);
+ else
+ (void)putchar(*fmt);
+ }
+ return 0;
+}
+void emit(Node p) {
+ for (; p; p = p->x.next) {
+ assert(p->x.registered);
+ if ((p->x.equatable && requate(p)) || moveself(p))
+ ;
+ else
+ (*emitter)(p, p->x.inst);
+ p->x.emitted = 1;
+ }
+}
+static int moveself(Node p) {
+ return p->x.copy
+ && p->syms[RX]->x.name == p->x.kids[0]->syms[RX]->x.name;
+}
+int move(Node p) {
+ p->x.copy = 1;
+ return 1;
+}
+static int requate(Node q) {
+ Symbol src = q->x.kids[0]->syms[RX];
+ Symbol tmp = q->syms[RX];
+ Node p;
+ int n = 0;
+
+ debug(fprint(stderr, "(requate(%x): tmp=%s src=%s)\n", q, tmp->x.name, src->x.name));
+ for (p = q->x.next; p; p = p->x.next)
+ if (p->x.copy && p->syms[RX] == src
+ && p->x.kids[0]->syms[RX] == tmp)
+ debug(fprint(stderr, "(requate arm 0 at %x)\n", p)),
+ p->syms[RX] = tmp;
+ else if (setsrc(p->syms[RX]) && !moveself(p) && !readsreg(p))
+ return 0;
+ else if (p->x.spills)
+ return 0;
+ else if (generic(p->op) == CALL && p->x.next)
+ return 0;
+ else if (p->op == LABEL+V && p->x.next)
+ return 0;
+ else if (p->syms[RX] == tmp && readsreg(p))
+ debug(fprint(stderr, "(requate arm 5 at %x)\n", p)),
+ n++;
+ else if (p->syms[RX] == tmp)
+ break;
+ debug(fprint(stderr, "(requate arm 7 at %x)\n", p));
+ assert(n > 0);
+ for (p = q->x.next; p; p = p->x.next)
+ if (p->syms[RX] == tmp && readsreg(p)) {
+ p->syms[RX] = src;
+ if (--n <= 0)
+ break;
+ }
+ return 1;
+}
+static void prelabel(Node p) {
+ if (p == NULL)
+ return;
+ prelabel(p->kids[0]);
+ prelabel(p->kids[1]);
+ if (NeedsReg[opindex(p->op)])
+ setreg(p, (*IR->x.rmap)(opkind(p->op)));
+ switch (generic(p->op)) {
+ case ADDRF: case ADDRL:
+ if (p->syms[0]->sclass == REGISTER)
+ p->op = VREG+P;
+ break;
+ case INDIR:
+ if (p->kids[0]->op == VREG+P)
+ setreg(p, p->kids[0]->syms[0]);
+ break;
+ case ASGN:
+ if (p->kids[0]->op == VREG+P)
+ rtarget(p, 1, p->kids[0]->syms[0]);
+ break;
+ case CVI: case CVU: case CVP:
+ if (optype(p->op) != F
+ && opsize(p->op) <= p->syms[0]->u.c.v.i)
+ p->op = LOAD + opkind(p->op);
+ break;
+ }
+ (IR->x.target)(p);
+}
+void setreg(Node p, Symbol r) {
+ p->syms[RX] = r;
+}
+void rtarget(Node p, int n, Symbol r) {
+ Node q = p->kids[n];
+
+ assert(q);
+ assert(r);
+ assert(r->sclass == REGISTER || !r->x.wildcard);
+ assert(q->syms[RX]);
+ if (r != q->syms[RX] && !q->syms[RX]->x.wildcard) {
+ q = newnode(LOAD + opkind(q->op),
+ q, NULL, q->syms[0]);
+ if (r->u.t.cse == p->kids[n])
+ r->u.t.cse = q;
+ p->kids[n] = p->x.kids[n] = q;
+ q->x.kids[0] = q->kids[0];
+ }
+ setreg(q, r);
+ debug(fprint(stderr, "(targeting %x->x.kids[%d]=%x to %s)\n", p, n, p->kids[n], r->x.name));
+}
+static void rewrite(Node p) {
+ assert(p->x.inst == 0);
+ prelabel(p);
+ debug(dumptree(p));
+ debug(fprint(stderr, "\n"));
+ (*IR->x._label)(p);
+ debug(dumpcover(p, 1, 0));
+ reduce(p, 1);
+}
+Node gen(Node forest) {
+ int i;
+ struct node sentinel;
+ Node dummy, p;
+
+ head = forest;
+ for (p = forest; p; p = p->link) {
+ assert(p->count == 0);
+ if (generic(p->op) == CALL)
+ docall(p);
+ else if ( generic(p->op) == ASGN
+ && generic(p->kids[1]->op) == CALL)
+ docall(p->kids[1]);
+ else if (generic(p->op) == ARG)
+ (*IR->x.doarg)(p);
+ rewrite(p);
+ p->x.listed = 1;
+ }
+ for (p = forest; p; p = p->link)
+ prune(p, &dummy);
+ relink(&sentinel, &sentinel);
+ for (p = forest; p; p = p->link)
+ linearize(p, &sentinel);
+ forest = sentinel.x.next;
+ assert(forest);
+ sentinel.x.next->x.prev = NULL;
+ sentinel.x.prev->x.next = NULL;
+ for (p = forest; p; p = p->x.next)
+ for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) {
+ assert(p->x.kids[i]->syms[RX]);
+ if (p->x.kids[i]->syms[RX]->temporary) {
+ p->x.kids[i]->x.prevuse =
+ p->x.kids[i]->syms[RX]->x.lastuse;
+ p->x.kids[i]->syms[RX]->x.lastuse = p->x.kids[i];
+ }
+ }
+ for (p = forest; p; p = p->x.next) {
+ ralloc(p);
+ if (p->x.listed && NeedsReg[opindex(p->op)]
+ && (*IR->x.rmap)(opkind(p->op))) {
+ assert(generic(p->op) == CALL || generic(p->op) == LOAD);
+ putreg(p->syms[RX]);
+ }
+ }
+ return forest;
+}
+int notarget(Node p) {
+ return p->syms[RX]->x.wildcard ? 0 : LBURG_MAX;
+}
+static void putreg(Symbol r) {
+ assert(r && r->x.regnode);
+ freemask[r->x.regnode->set] |= r->x.regnode->mask;
+ debug(dumpregs("(freeing %s)\n", r->x.name, NULL));
+}
+static Symbol askfixedreg(Symbol s) {
+ Regnode r = s->x.regnode;
+ int n = r->set;
+
+ if (r->mask&~freemask[n])
+ return NULL;
+ else {
+ freemask[n] &= ~r->mask;
+ usedmask[n] |= r->mask;
+ return s;
+ }
+}
+static Symbol askreg(Symbol rs, unsigned rmask[]) {
+ int i;
+
+ if (rs->x.wildcard == NULL)
+ return askfixedreg(rs);
+ for (i = 31; i >= 0; i--) {
+ Symbol r = rs->x.wildcard[i];
+ if (r != NULL
+ && !(r->x.regnode->mask&~rmask[r->x.regnode->set])
+ && askfixedreg(r))
+ return r;
+ }
+ return NULL;
+}
+
+static Symbol getreg(Symbol s, unsigned mask[], Node p) {
+ Symbol r = askreg(s, mask);
+ if (r == NULL) {
+ r = spillee(s, mask, p);
+ assert(r && r->x.regnode);
+ spill(r->x.regnode->mask, r->x.regnode->set, p);
+ r = askreg(s, mask);
+ }
+ assert(r && r->x.regnode);
+ r->x.regnode->vbl = NULL;
+ return r;
+}
+int askregvar(Symbol p, Symbol regs) {
+ Symbol r;
+
+ assert(p);
+ if (p->sclass != REGISTER)
+ return 0;
+ else if (!isscalar(p->type)) {
+ p->sclass = AUTO;
+ return 0;
+ }
+ else if (p->temporary) {
+ p->x.name = "?";
+ return 1;
+ }
+ else if ((r = askreg(regs, vmask)) != NULL) {
+ p->x.regnode = r->x.regnode;
+ p->x.regnode->vbl = p;
+ p->x.name = r->x.name;
+ debug(dumpregs("(allocating %s to symbol %s)\n", p->x.name, p->name));
+ return 1;
+ }
+ else {
+ p->sclass = AUTO;
+ return 0;
+ }
+}
+static void linearize(Node p, Node next) {
+ int i;
+
+ for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++)
+ linearize(p->x.kids[i], next);
+ relink(next->x.prev, p);
+ relink(p, next);
+ debug(fprint(stderr, "(listing %x)\n", p));
+}
+static void ralloc(Node p) {
+ int i;
+ unsigned mask[2];
+
+ mask[0] = tmask[0];
+ mask[1] = tmask[1];
+ assert(p);
+ debug(fprint(stderr, "(rallocing %x)\n", p));
+ for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) {
+ Node kid = p->x.kids[i];
+ Symbol r = kid->syms[RX];
+ assert(r && kid->x.registered);
+ if (r->sclass != REGISTER && r->x.lastuse == kid)
+ putreg(r);
+ }
+ if (!p->x.registered && NeedsReg[opindex(p->op)]
+ && (*IR->x.rmap)(opkind(p->op))) {
+ Symbol sym = p->syms[RX], set = sym;
+ assert(sym);
+ if (sym->temporary)
+ set = (*IR->x.rmap)(opkind(p->op));
+ assert(set);
+ if (set->sclass != REGISTER) {
+ Symbol r;
+ if (*IR->x._templates[getrule(p, p->x.inst)] == '?')
+ for (i = 1; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) {
+ Symbol r = p->x.kids[i]->syms[RX];
+ assert(p->x.kids[i]->x.registered);
+ assert(r && r->x.regnode);
+ assert(sym->x.wildcard || sym != r);
+ mask[r->x.regnode->set] &= ~r->x.regnode->mask;
+ }
+ r = getreg(set, mask, p);
+ if (sym->temporary) {
+ Node q;
+ r->x.lastuse = sym->x.lastuse;
+ for (q = sym->x.lastuse; q; q = q->x.prevuse) {
+ q->syms[RX] = r;
+ q->x.registered = 1;
+ if (sym->u.t.cse && q->x.copy)
+ q->x.equatable = 1;
+ }
+ } else {
+ p->syms[RX] = r;
+ r->x.lastuse = p;
+ }
+ debug(dumpregs("(allocating %s to node %x)\n", r->x.name, (char *) p));
+ }
+ }
+ p->x.registered = 1;
+ (*IR->x.clobber)(p);
+}
+static Symbol spillee(Symbol set, unsigned mask[], Node here) {
+ Symbol bestreg = NULL;
+ int bestdist = -1, i;
+
+ assert(set);
+ if (!set->x.wildcard)
+ bestreg = set;
+ else {
+ for (i = 31; i >= 0; i--) {
+ Symbol ri = set->x.wildcard[i];
+ if (
+ ri != NULL &&
+ ri->x.lastuse &&
+ (ri->x.regnode->mask&tmask[ri->x.regnode->set]&mask[ri->x.regnode->set])
+ ) {
+ Regnode rn = ri->x.regnode;
+ Node q = here;
+ int dist = 0;
+ for (; q && !uses(q, rn); q = q->x.next)
+ dist++;
+ if (q && dist > bestdist) {
+ bestdist = dist;
+ bestreg = ri;
+ }
+ }
+ }
+ }
+ assert(bestreg); /* Must be able to spill something. Reconfigure the register allocator
+ to ensure that we can allocate a register for all nodes without spilling
+ the node's necessary input regs. */
+ assert(bestreg->x.regnode->vbl == NULL); /* Can't spill register variables because
+ the reload site might be in other blocks. Reconfigure the register allocator
+ to ensure that this register is never allocated to a variable. */
+ return bestreg;
+}
+static int uses(Node p, Regnode rn) {
+ int i;
+
+ for (i = 0; i < NELEMS(p->x.kids); i++)
+ if (
+ p->x.kids[i] &&
+ p->x.kids[i]->x.registered &&
+ rn->set == p->x.kids[i]->syms[RX]->x.regnode->set &&
+ (rn->mask&p->x.kids[i]->syms[RX]->x.regnode->mask)
+ )
+ return 1;
+ return 0;
+}
+static void spillr(Symbol r, Node here) {
+ int i;
+ Symbol tmp;
+ Node p = r->x.lastuse;
+ assert(p);
+ while (p->x.prevuse)
+ assert(r == p->syms[RX]),
+ p = p->x.prevuse;
+ assert(p->x.registered && !readsreg(p));
+ tmp = newtemp(AUTO, optype(p->op), opsize(p->op));
+ genspill(r, p, tmp);
+ for (p = here->x.next; p; p = p->x.next)
+ for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) {
+ Node k = p->x.kids[i];
+ if (k->x.registered && k->syms[RX] == r)
+ genreload(p, tmp, i);
+ }
+ putreg(r);
+}
+static void genspill(Symbol r, Node last, Symbol tmp) {
+ Node p, q;
+ Symbol s;
+ unsigned ty;
+
+ debug(fprint(stderr, "(spilling %s to local %s)\n", r->x.name, tmp->x.name));
+ debug(fprint(stderr, "(genspill: "));
+ debug(dumptree(last));
+ debug(fprint(stderr, ")\n"));
+ ty = opkind(last->op);
+ NEW0(s, FUNC);
+ s->sclass = REGISTER;
+ s->name = s->x.name = r->x.name;
+ s->x.regnode = r->x.regnode;
+ q = newnode(ADDRL+P + sizeop(IR->ptrmetric.size), NULL, NULL, s);
+ q = newnode(INDIR + ty, q, NULL, NULL);
+ p = newnode(ADDRL+P + sizeop(IR->ptrmetric.size), NULL, NULL, tmp);
+ p = newnode(ASGN + ty, p, q, NULL);
+ p->x.spills = 1;
+ rewrite(p);
+ prune(p, &q);
+ q = last->x.next;
+ linearize(p, q);
+ for (p = last->x.next; p != q; p = p->x.next) {
+ ralloc(p);
+ assert(!p->x.listed || !NeedsReg[opindex(p->op)] || !(*IR->x.rmap)(opkind(p->op)));
+ }
+}
+
+static void genreload(Node p, Symbol tmp, int i) {
+ Node q;
+ int ty;
+
+ debug(fprint(stderr, "(replacing %x with a reload from %s)\n", p->x.kids[i], tmp->x.name));
+ debug(fprint(stderr, "(genreload: "));
+ debug(dumptree(p->x.kids[i]));
+ debug(fprint(stderr, ")\n"));
+ ty = opkind(p->x.kids[i]->op);
+ q = newnode(ADDRL+P + sizeop(IR->ptrmetric.size), NULL, NULL, tmp);
+ p->x.kids[i] = newnode(INDIR + ty, q, NULL, NULL);
+ rewrite(p->x.kids[i]);
+ prune(p->x.kids[i], &q);
+ reprune(&p->kids[1], reprune(&p->kids[0], 0, i, p), i, p);
+ prune(p, &q);
+ linearize(p->x.kids[i], p);
+}
+static int reprune(Node *pp, int k, int n, Node p) {
+ struct node x, *q = *pp;
+
+ if (q == NULL || k > n)
+ return k;
+ else if (q->x.inst == 0)
+ return reprune(&q->kids[1],
+ reprune(&q->kids[0], k, n, p), n, p);
+ if (k == n) {
+ debug(fprint(stderr, "(reprune changes %x from %x to %x)\n", pp, *pp, p->x.kids[n]));
+ *pp = p->x.kids[n];
+ x = *p;
+ (IR->x.target)(&x);
+ }
+ return k + 1;
+}
+void spill(unsigned mask, int n, Node here) {
+ int i;
+ Node p;
+
+ here->x.spills = 1;
+ usedmask[n] |= mask;
+ if (mask&~freemask[n]) {
+
+ assert( /* It makes no sense for a node to clobber() its target. */
+ here->x.registered == 0 || /* call isn't coming through clobber() */
+ here->syms[RX] == NULL ||
+ here->syms[RX]->x.regnode == NULL ||
+ here->syms[RX]->x.regnode->set != n ||
+ (here->syms[RX]->x.regnode->mask&mask) == 0
+ );
+
+ for (p = here; p; p = p->x.next)
+ for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) {
+ Symbol r = p->x.kids[i]->syms[RX];
+ assert(r);
+ if (p->x.kids[i]->x.registered && r->x.regnode->set == n
+ && r->x.regnode->mask&mask)
+ spillr(r, here);
+ }
+ }
+}
+static void dumpregs(char *msg, char *a, char *b) {
+ fprint(stderr, msg, a, b);
+ fprint(stderr, "(free[0]=%x)\n", freemask[0]);
+ fprint(stderr, "(free[1]=%x)\n", freemask[1]);
+}
+
+int getregnum(Node p) {
+ assert(p && p->syms[RX] && p->syms[RX]->x.regnode);
+ return p->syms[RX]->x.regnode->number;
+}
+
+
+unsigned regloc(Symbol p) {
+ assert(p && p->sclass == REGISTER && p->sclass == REGISTER && p->x.regnode);
+ return p->x.regnode->set<<8 | p->x.regnode->number;
+}
+
diff --git a/src/tools/lcc/src/init.c b/src/tools/lcc/src/init.c
new file mode 100644
index 0000000..172d7c0
--- /dev/null
+++ b/src/tools/lcc/src/init.c
@@ -0,0 +1,318 @@
+#include "c.h"
+
+
+static int curseg; /* current segment */
+
+/* defpointer - initialize a pointer to p or to 0 if p==0 */
+void defpointer(Symbol p) {
+ if (p) {
+ (*IR->defaddress)(p);
+ p->ref++;
+ } else {
+ static Value v;
+ (*IR->defconst)(P, voidptype->size, v);
+ }
+}
+
+/* genconst - generate/check constant expression e; return size */
+static int genconst(Tree e, int def) {
+ for (;;)
+ switch (generic(e->op)) {
+ case ADDRG:
+ if (def)
+ (*IR->defaddress)(e->u.sym);
+ return e->type->size;
+ case CNST:
+ if (e->op == CNST+P && isarray(e->type)) {
+ e = cvtconst(e);
+ continue;
+ }
+ if (def)
+ (*IR->defconst)(e->type->op, e->type->size, e->u.v);
+ return e->type->size;
+ case RIGHT:
+ assert(e->kids[0] || e->kids[1]);
+ if (e->kids[1] && e->kids[0])
+ error("initializer must be constant\n");
+ e = e->kids[1] ? e->kids[1] : e->kids[0];
+ continue;
+ case CVP:
+ if (isarith(e->type))
+ error("cast from `%t' to `%t' is illegal in constant expressions\n",
+ e->kids[0]->type, e->type);
+ /* fall thru */
+ case CVI: case CVU: case CVF:
+ e = e->kids[0];
+ continue;
+ default:
+ error("initializer must be constant\n");
+ if (def)
+ genconst(consttree(0, inttype), def);
+ return inttype->size;
+ }
+}
+
+/* initvalue - evaluate a constant expression for a value of integer type ty */
+static Tree initvalue(Type ty) {
+ Type aty;
+ Tree e;
+
+ needconst++;
+ e = expr1(0);
+ if ((aty = assign(ty, e)) != NULL)
+ e = cast(e, aty);
+ else {
+ error("invalid initialization type; found `%t' expected `%t'\n",
+ e->type, ty);
+ e = retype(consttree(0, inttype), ty);
+ }
+ needconst--;
+ if (generic(e->op) != CNST) {
+ error("initializer must be constant\n");
+ e = retype(consttree(0, inttype), ty);
+ }
+ return e;
+}
+
+/* initarray - initialize array of ty of <= len bytes; if len == 0, go to } */
+static int initarray(int len, Type ty, int lev) {
+ int n = 0;
+
+ do {
+ initializer(ty, lev);
+ n += ty->size;
+ if ((len > 0 && n >= len) || t != ',')
+ break;
+ t = gettok();
+ } while (t != '}');
+ return n;
+}
+
+/* initchar - initialize array of <= len ty characters; if len == 0, go to } */
+static int initchar(int len, Type ty) {
+ int n = 0;
+ char buf[16], *s = buf;
+
+ do {
+ *s++ = initvalue(ty)->u.v.i;
+ if (++n%inttype->size == 0) {
+ (*IR->defstring)(inttype->size, buf);
+ s = buf;
+ }
+ if ((len > 0 && n >= len) || t != ',')
+ break;
+ t = gettok();
+ } while (t != '}');
+ if (s > buf)
+ (*IR->defstring)(s - buf, buf);
+ return n;
+}
+
+/* initend - finish off an initialization at level lev; accepts trailing comma */
+static void initend(int lev, char follow[]) {
+ if (lev == 0 && t == ',')
+ t = gettok();
+ test('}', follow);
+}
+
+/* initfields - initialize <= an unsigned's worth of bit fields in fields p to q */
+static int initfields(Field p, Field q) {
+ unsigned int bits = 0;
+ int i, n = 0;
+
+ do {
+ i = initvalue(inttype)->u.v.i;
+ if (fieldsize(p) < 8*p->type->size) {
+ if ((p->type == inttype &&
+ (i < -(int)(fieldmask(p)>>1)-1 || i > (int)(fieldmask(p)>>1)))
+ || (p->type == unsignedtype && (i&~fieldmask(p)) != 0))
+ warning("initializer exceeds bit-field width\n");
+ i &= fieldmask(p);
+ }
+ bits |= i<<fieldright(p);
+ if (IR->little_endian) {
+ if (fieldsize(p) + fieldright(p) > n)
+ n = fieldsize(p) + fieldright(p);
+ } else {
+ if (fieldsize(p) + fieldleft(p) > n)
+ n = fieldsize(p) + fieldleft(p);
+ }
+ if (p->link == q)
+ break;
+ p = p->link;
+ } while (t == ',' && (t = gettok()) != 0);
+ n = (n + 7)/8;
+ for (i = 0; i < n; i++) {
+ Value v;
+ if (IR->little_endian) {
+ v.u = (unsigned char)bits;
+ bits >>= 8;
+ } else { /* a big endian */
+ v.u = (unsigned char)(bits>>(8*(unsignedtype->size - 1)));
+ bits <<= 8;
+ }
+ (*IR->defconst)(U, unsignedchar->size, v);
+ }
+ return n;
+}
+
+/* initstruct - initialize a struct ty of <= len bytes; if len == 0, go to } */
+static int initstruct(int len, Type ty, int lev) {
+ int a, n = 0;
+ Field p = ty->u.sym->u.s.flist;
+
+ do {
+ if (p->offset > n) {
+ (*IR->space)(p->offset - n);
+ n += p->offset - n;
+ }
+ if (p->lsb) {
+ Field q = p;
+ while (q->link && q->link->offset == p->offset)
+ q = q->link;
+ n += initfields(p, q->link);
+ p = q;
+ } else {
+ initializer(p->type, lev);
+ n += p->type->size;
+ }
+ if (p->link) {
+ p = p->link;
+ a = p->type->align;
+ } else
+ a = ty->align;
+ if (a && n%a) {
+ (*IR->space)(a - n%a);
+ n = roundup(n, a);
+ }
+ if ((len > 0 && n >= len) || t != ',')
+ break;
+ t = gettok();
+ } while (t != '}');
+ return n;
+}
+
+/* initializer - constexpr | { constexpr ( , constexpr )* [ , ] } */
+Type initializer(Type ty, int lev) {
+ int n = 0;
+ Tree e;
+ Type aty = NULL;
+ static char follow[] = { IF, CHAR, STATIC, 0 };
+
+ ty = unqual(ty);
+ if (isscalar(ty)) {
+ needconst++;
+ if (t == '{') {
+ t = gettok();
+ e = expr1(0);
+ initend(lev, follow);
+ } else
+ e = expr1(0);
+ e = pointer(e);
+ if ((aty = assign(ty, e)) != NULL)
+ e = cast(e, aty);
+ else
+ error("invalid initialization type; found `%t' expected `%t'\n",
+ e->type, ty);
+ n = genconst(e, 1);
+ deallocate(STMT);
+ needconst--;
+ }
+ if ((isunion(ty) || isstruct(ty)) && ty->size == 0) {
+ static char follow[] = { CHAR, STATIC, 0 };
+ error("cannot initialize undefined `%t'\n", ty);
+ skipto(';', follow);
+ return ty;
+ } else if (isunion(ty)) {
+ if (t == '{') {
+ t = gettok();
+ n = initstruct(ty->u.sym->u.s.flist->type->size, ty, lev + 1);
+ initend(lev, follow);
+ } else {
+ if (lev == 0)
+ error("missing { in initialization of `%t'\n", ty);
+ n = initstruct(ty->u.sym->u.s.flist->type->size, ty, lev + 1);
+ }
+ } else if (isstruct(ty)) {
+ if (t == '{') {
+ t = gettok();
+ n = initstruct(0, ty, lev + 1);
+ test('}', follow);
+ } else if (lev > 0)
+ n = initstruct(ty->size, ty, lev + 1);
+ else {
+ error("missing { in initialization of `%t'\n", ty);
+ n = initstruct(ty->u.sym->u.s.flist->type->size, ty, lev + 1);
+ }
+ }
+ if (isarray(ty))
+ aty = unqual(ty->type);
+ if (isarray(ty) && ischar(aty)) {
+ if (t == SCON) {
+ if (ty->size > 0 && ty->size == tsym->type->size - 1)
+ tsym->type = array(chartype, ty->size, 0);
+ n = tsym->type->size;
+ (*IR->defstring)(tsym->type->size, tsym->u.c.v.p);
+ t = gettok();
+ } else if (t == '{') {
+ t = gettok();
+ if (t == SCON) {
+ ty = initializer(ty, lev + 1);
+ initend(lev, follow);
+ return ty;
+ }
+ n = initchar(0, aty);
+ test('}', follow);
+ } else if (lev > 0 && ty->size > 0)
+ n = initchar(ty->size, aty);
+ else { /* eg, char c[] = 0; */
+ error("missing { in initialization of `%t'\n", ty);
+ n = initchar(1, aty);
+ }
+ } else if (isarray(ty)) {
+ if (t == SCON && aty == widechar) {
+ int i;
+ unsigned int *s = tsym->u.c.v.p;
+ if (ty->size > 0 && ty->size == tsym->type->size - widechar->size)
+ tsym->type = array(widechar, ty->size/widechar->size, 0);
+ n = tsym->type->size;
+ for (i = 0; i < n; i += widechar->size) {
+ Value v;
+ v.u = *s++;
+ (*IR->defconst)(widechar->op, widechar->size, v);
+ }
+ t = gettok();
+ } else if (t == '{') {
+ t = gettok();
+ if (t == SCON && aty == widechar) {
+ ty = initializer(ty, lev + 1);
+ initend(lev, follow);
+ return ty;
+ }
+ n = initarray(0, aty, lev + 1);
+ test('}', follow);
+ } else if (lev > 0 && ty->size > 0)
+ n = initarray(ty->size, aty, lev + 1);
+ else {
+ error("missing { in initialization of `%t'\n", ty);
+ n = initarray(aty->size, aty, lev + 1);
+ }
+ }
+ if (ty->size) {
+ if (n > ty->size)
+ error("too many initializers\n");
+ else if (n < ty->size)
+ (*IR->space)(ty->size - n);
+ } else if (isarray(ty) && ty->type->size > 0)
+ ty = array(ty->type, n/ty->type->size, 0);
+ else
+ ty->size = n;
+ return ty;
+}
+
+/* swtoseg - switch to segment seg, if necessary */
+void swtoseg(int seg) {
+ if (curseg != seg)
+ (*IR->segment)(seg);
+ curseg = seg;
+}
diff --git a/src/tools/lcc/src/inits.c b/src/tools/lcc/src/inits.c
new file mode 100644
index 0000000..c42f61e
--- /dev/null
+++ b/src/tools/lcc/src/inits.c
@@ -0,0 +1,7 @@
+void init(int argc, char *argv[]) {
+ {extern void input_init(int, char *[]); input_init(argc, argv);}
+ {extern void main_init(int, char *[]); main_init(argc, argv);}
+ {extern void prof_init(int, char *[]); prof_init(argc, argv);}
+ {extern void trace_init(int, char *[]); trace_init(argc, argv);}
+ {extern void type_init(int, char *[]); type_init(argc, argv);}
+}
diff --git a/src/tools/lcc/src/input.c b/src/tools/lcc/src/input.c
new file mode 100644
index 0000000..c2a084e
--- /dev/null
+++ b/src/tools/lcc/src/input.c
@@ -0,0 +1,135 @@
+#include "c.h"
+
+
+static void pragma(void);
+static void resynch(void);
+
+static int bsize;
+static unsigned char buffer[MAXLINE+1 + BUFSIZE+1];
+unsigned char *cp; /* current input character */
+char *file; /* current input file name */
+char *firstfile; /* first input file */
+unsigned char *limit; /* points to last character + 1 */
+char *line; /* current line */
+int lineno; /* line number of current line */
+
+void nextline(void) {
+ do {
+ if (cp >= limit) {
+ fillbuf();
+ if (cp >= limit)
+ cp = limit;
+ if (cp == limit)
+ return;
+ } else {
+ lineno++;
+ for (line = (char *)cp; *cp==' ' || *cp=='\t'; cp++)
+ ;
+ if (*cp == '#') {
+ resynch();
+ nextline();
+ }
+ }
+ } while (*cp == '\n' && cp == limit);
+}
+void fillbuf(void) {
+ if (bsize == 0)
+ return;
+ if (cp >= limit)
+ cp = &buffer[MAXLINE+1];
+ else
+ {
+ int n = limit - cp;
+ unsigned char *s = &buffer[MAXLINE+1] - n;
+ assert(s >= buffer);
+ line = (char *)s - ((char *)cp - line);
+ while (cp < limit)
+ *s++ = *cp++;
+ cp = &buffer[MAXLINE+1] - n;
+ }
+ if (feof(stdin))
+ bsize = 0;
+ else
+ bsize = fread(&buffer[MAXLINE+1], 1, BUFSIZE, stdin);
+ if (bsize < 0) {
+ error("read error\n");
+ exit(EXIT_FAILURE);
+ }
+ limit = &buffer[MAXLINE+1+bsize];
+ *limit = '\n';
+}
+void input_init(int argc, char *argv[]) {
+ static int inited;
+
+ if (inited)
+ return;
+ inited = 1;
+ main_init(argc, argv);
+ limit = cp = &buffer[MAXLINE+1];
+ bsize = -1;
+ lineno = 0;
+ file = NULL;
+ fillbuf();
+ if (cp >= limit)
+ cp = limit;
+ nextline();
+}
+
+/* pragma - handle #pragma ref id... */
+static void pragma(void) {
+ if ((t = gettok()) == ID && strcmp(token, "ref") == 0)
+ for (;;) {
+ while (*cp == ' ' || *cp == '\t')
+ cp++;
+ if (*cp == '\n' || *cp == 0)
+ break;
+ if ((t = gettok()) == ID && tsym) {
+ tsym->ref++;
+ use(tsym, src);
+ }
+ }
+}
+
+/* resynch - set line number/file name in # n [ "file" ] and #pragma ... */
+static void resynch(void) {
+ for (cp++; *cp == ' ' || *cp == '\t'; )
+ cp++;
+ if (limit - cp < MAXLINE)
+ fillbuf();
+ if (strncmp((char *)cp, "pragma", 6) == 0) {
+ cp += 6;
+ pragma();
+ } else if (*cp >= '0' && *cp <= '9') {
+ line: for (lineno = 0; *cp >= '0' && *cp <= '9'; )
+ lineno = 10*lineno + *cp++ - '0';
+ lineno--;
+ while (*cp == ' ' || *cp == '\t')
+ cp++;
+ if (*cp == '"') {
+ file = (char *)++cp;
+ while (*cp && *cp != '"' && *cp != '\n')
+ cp++;
+ file = stringn(file, (char *)cp - file);
+ if (*cp == '\n')
+ warning("missing \" in preprocessor line\n");
+ if (firstfile == 0)
+ firstfile = file;
+ }
+ } else if (strncmp((char *)cp, "line", 4) == 0) {
+ for (cp += 4; *cp == ' ' || *cp == '\t'; )
+ cp++;
+ if (*cp >= '0' && *cp <= '9')
+ goto line;
+ if (Aflag >= 2)
+ warning("unrecognized control line\n");
+ } else if (Aflag >= 2 && *cp != '\n')
+ warning("unrecognized control line\n");
+ while (*cp)
+ if (*cp++ == '\n') {
+ if (cp == limit + 1)
+ nextline();
+ else
+ break;
+ }
+}
+
diff --git a/src/tools/lcc/src/lex.c b/src/tools/lcc/src/lex.c
new file mode 100644
index 0000000..ec2f1ec
--- /dev/null
+++ b/src/tools/lcc/src/lex.c
@@ -0,0 +1,923 @@
+#include "c.h"
+#include <float.h>
+#include <errno.h>
+
+
+#define MAXTOKEN 32
+
+enum { BLANK=01, NEWLINE=02, LETTER=04,
+ DIGIT=010, HEX=020, OTHER=040 };
+
+static unsigned char map[256] = { /* 000 nul */ 0,
+ /* 001 soh */ 0,
+ /* 002 stx */ 0,
+ /* 003 etx */ 0,
+ /* 004 eot */ 0,
+ /* 005 enq */ 0,
+ /* 006 ack */ 0,
+ /* 007 bel */ 0,
+ /* 010 bs */ 0,
+ /* 011 ht */ BLANK,
+ /* 012 nl */ NEWLINE,
+ /* 013 vt */ BLANK,
+ /* 014 ff */ BLANK,
+ /* 015 cr */ 0,
+ /* 016 so */ 0,
+ /* 017 si */ 0,
+ /* 020 dle */ 0,
+ /* 021 dc1 */ 0,
+ /* 022 dc2 */ 0,
+ /* 023 dc3 */ 0,
+ /* 024 dc4 */ 0,
+ /* 025 nak */ 0,
+ /* 026 syn */ 0,
+ /* 027 etb */ 0,
+ /* 030 can */ 0,
+ /* 031 em */ 0,
+ /* 032 sub */ 0,
+ /* 033 esc */ 0,
+ /* 034 fs */ 0,
+ /* 035 gs */ 0,
+ /* 036 rs */ 0,
+ /* 037 us */ 0,
+ /* 040 sp */ BLANK,
+ /* 041 ! */ OTHER,
+ /* 042 " */ OTHER,
+ /* 043 # */ OTHER,
+ /* 044 $ */ 0,
+ /* 045 % */ OTHER,
+ /* 046 & */ OTHER,
+ /* 047 ' */ OTHER,
+ /* 050 ( */ OTHER,
+ /* 051 ) */ OTHER,
+ /* 052 * */ OTHER,
+ /* 053 + */ OTHER,
+ /* 054 , */ OTHER,
+ /* 055 - */ OTHER,
+ /* 056 . */ OTHER,
+ /* 057 / */ OTHER,
+ /* 060 0 */ DIGIT,
+ /* 061 1 */ DIGIT,
+ /* 062 2 */ DIGIT,
+ /* 063 3 */ DIGIT,
+ /* 064 4 */ DIGIT,
+ /* 065 5 */ DIGIT,
+ /* 066 6 */ DIGIT,
+ /* 067 7 */ DIGIT,
+ /* 070 8 */ DIGIT,
+ /* 071 9 */ DIGIT,
+ /* 072 : */ OTHER,
+ /* 073 ; */ OTHER,
+ /* 074 < */ OTHER,
+ /* 075 = */ OTHER,
+ /* 076 > */ OTHER,
+ /* 077 ? */ OTHER,
+ /* 100 @ */ 0,
+ /* 101 A */ LETTER|HEX,
+ /* 102 B */ LETTER|HEX,
+ /* 103 C */ LETTER|HEX,
+ /* 104 D */ LETTER|HEX,
+ /* 105 E */ LETTER|HEX,
+ /* 106 F */ LETTER|HEX,
+ /* 107 G */ LETTER,
+ /* 110 H */ LETTER,
+ /* 111 I */ LETTER,
+ /* 112 J */ LETTER,
+ /* 113 K */ LETTER,
+ /* 114 L */ LETTER,
+ /* 115 M */ LETTER,
+ /* 116 N */ LETTER,
+ /* 117 O */ LETTER,
+ /* 120 P */ LETTER,
+ /* 121 Q */ LETTER,
+ /* 122 R */ LETTER,
+ /* 123 S */ LETTER,
+ /* 124 T */ LETTER,
+ /* 125 U */ LETTER,
+ /* 126 V */ LETTER,
+ /* 127 W */ LETTER,
+ /* 130 X */ LETTER,
+ /* 131 Y */ LETTER,
+ /* 132 Z */ LETTER,
+ /* 133 [ */ OTHER,
+ /* 134 \ */ OTHER,
+ /* 135 ] */ OTHER,
+ /* 136 ^ */ OTHER,
+ /* 137 _ */ LETTER,
+ /* 140 ` */ 0,
+ /* 141 a */ LETTER|HEX,
+ /* 142 b */ LETTER|HEX,
+ /* 143 c */ LETTER|HEX,
+ /* 144 d */ LETTER|HEX,
+ /* 145 e */ LETTER|HEX,
+ /* 146 f */ LETTER|HEX,
+ /* 147 g */ LETTER,
+ /* 150 h */ LETTER,
+ /* 151 i */ LETTER,
+ /* 152 j */ LETTER,
+ /* 153 k */ LETTER,
+ /* 154 l */ LETTER,
+ /* 155 m */ LETTER,
+ /* 156 n */ LETTER,
+ /* 157 o */ LETTER,
+ /* 160 p */ LETTER,
+ /* 161 q */ LETTER,
+ /* 162 r */ LETTER,
+ /* 163 s */ LETTER,
+ /* 164 t */ LETTER,
+ /* 165 u */ LETTER,
+ /* 166 v */ LETTER,
+ /* 167 w */ LETTER,
+ /* 170 x */ LETTER,
+ /* 171 y */ LETTER,
+ /* 172 z */ LETTER,
+ /* 173 { */ OTHER,
+ /* 174 | */ OTHER,
+ /* 175 } */ OTHER,
+ /* 176 ~ */ OTHER, };
+static struct symbol tval;
+static char cbuf[BUFSIZE+1];
+static unsigned int wcbuf[BUFSIZE+1];
+
+Coordinate src; /* current source coordinate */
+int t;
+char *token; /* current token */
+Symbol tsym; /* symbol table entry for current token */
+
+static void *cput(int c, void *cl);
+static void *wcput(int c, void *cl);
+static void *scon(int q, void *put(int c, void *cl), void *cl);
+static int backslash(int q);
+static Symbol fcon(void);
+static Symbol icon(unsigned long, int, int);
+static void ppnumber(char *);
+
+int gettok(void) {
+ for (;;) {
+ register unsigned char *rcp = cp;
+ while (map[*rcp]&BLANK)
+ rcp++;
+ if (limit - rcp < MAXTOKEN) {
+ cp = rcp;
+ fillbuf();
+ rcp = cp;
+ }
+ src.file = file;
+ src.x = (char *)rcp - line;
+ src.y = lineno;
+ cp = rcp + 1;
+ switch (*rcp++) {
+ case '/': if (*rcp == '*') {
+ int c = 0;
+ for (rcp++; *rcp != '/' || c != '*'; )
+ if (map[*rcp]&NEWLINE) {
+ if (rcp < limit)
+ c = *rcp;
+ cp = rcp + 1;
+ nextline();
+ rcp = cp;
+ if (rcp == limit)
+ break;
+ } else
+ c = *rcp++;
+ if (rcp < limit)
+ rcp++;
+ else
+ error("unclosed comment\n");
+ cp = rcp;
+ continue;
+ }
+ return '/';
+ case '<':
+ if (*rcp == '=') return cp++, LEQ;
+ if (*rcp == '<') return cp++, LSHIFT;
+ return '<';
+ case '>':
+ if (*rcp == '=') return cp++, GEQ;
+ if (*rcp == '>') return cp++, RSHIFT;
+ return '>';
+ case '-':
+ if (*rcp == '>') return cp++, DEREF;
+ if (*rcp == '-') return cp++, DECR;
+ return '-';
+ case '=': return *rcp == '=' ? cp++, EQL : '=';
+ case '!': return *rcp == '=' ? cp++, NEQ : '!';
+ case '|': return *rcp == '|' ? cp++, OROR : '|';
+ case '&': return *rcp == '&' ? cp++, ANDAND : '&';
+ case '+': return *rcp == '+' ? cp++, INCR : '+';
+ case ';': case ',': case ':':
+ case '*': case '~': case '%': case '^': case '?':
+ case '[': case ']': case '{': case '}': case '(': case ')':
+ return rcp[-1];
+ case '\n': case '\v': case '\r': case '\f':
+ nextline();
+ if (cp == limit) {
+ tsym = NULL;
+ return EOI;
+ }
+ continue;
+
+ case 'i':
+ if (rcp[0] == 'f'
+ && !(map[rcp[1]]&(DIGIT|LETTER))) {
+ cp = rcp + 1;
+ return IF;
+ }
+ if (rcp[0] == 'n'
+ && rcp[1] == 't'
+ && !(map[rcp[2]]&(DIGIT|LETTER))) {
+ cp = rcp + 2;
+ tsym = inttype->u.sym;
+ return INT;
+ }
+ goto id;
+ case 'h': case 'j': case 'k': case 'm': case 'n': case 'o':
+ case 'p': case 'q': case 'x': case 'y': case 'z':
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ id:
+ if (limit - rcp < MAXLINE) {
+ cp = rcp - 1;
+ fillbuf();
+ rcp = ++cp;
+ }
+ assert(cp == rcp);
+ token = (char *)rcp - 1;
+ while (map[*rcp]&(DIGIT|LETTER))
+ rcp++;
+ token = stringn(token, (char *)rcp - token);
+ tsym = lookup(token, identifiers);
+ cp = rcp;
+ return ID;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9': {
+ unsigned long n = 0;
+ if (limit - rcp < MAXLINE) {
+ cp = rcp - 1;
+ fillbuf();
+ rcp = ++cp;
+ }
+ assert(cp == rcp);
+ token = (char *)rcp - 1;
+ if (*token == '0' && (*rcp == 'x' || *rcp == 'X')) {
+ int d, overflow = 0;
+ while (*++rcp) {
+ if (map[*rcp]&DIGIT)
+ d = *rcp - '0';
+ else if (*rcp >= 'a' && *rcp <= 'f')
+ d = *rcp - 'a' + 10;
+ else if (*rcp >= 'A' && *rcp <= 'F')
+ d = *rcp - 'A' + 10;
+ else
+ break;
+ if (n&~(~0UL >> 4))
+ overflow = 1;
+ else
+ n = (n<<4) + d;
+ }
+ if ((char *)rcp - token <= 2)
+ error("invalid hexadecimal constant `%S'\n", token, (char *)rcp-token);
+ cp = rcp;
+ tsym = icon(n, overflow, 16);
+ } else if (*token == '0') {
+ int err = 0, overflow = 0;
+ for ( ; map[*rcp]&DIGIT; rcp++) {
+ if (*rcp == '8' || *rcp == '9')
+ err = 1;
+ if (n&~(~0UL >> 3))
+ overflow = 1;
+ else
+ n = (n<<3) + (*rcp - '0');
+ }
+ if (*rcp == '.' || *rcp == 'e' || *rcp == 'E') {
+ cp = rcp;
+ tsym = fcon();
+ return FCON;
+ }
+ cp = rcp;
+ tsym = icon(n, overflow, 8);
+ if (err)
+ error("invalid octal constant `%S'\n", token, (char*)cp-token);
+ } else {
+ int overflow = 0;
+ for (n = *token - '0'; map[*rcp]&DIGIT; ) {
+ int d = *rcp++ - '0';
+ if (n > (ULONG_MAX - d)/10)
+ overflow = 1;
+ else
+ n = 10*n + d;
+ }
+ if (*rcp == '.' || *rcp == 'e' || *rcp == 'E') {
+ cp = rcp;
+ tsym = fcon();
+ return FCON;
+ }
+ cp = rcp;
+ tsym = icon(n, overflow, 10);
+ }
+ return ICON;
+ }
+ case '.':
+ if (rcp[0] == '.' && rcp[1] == '.') {
+ cp += 2;
+ return ELLIPSIS;
+ }
+ if ((map[*rcp]&DIGIT) == 0)
+ return '.';
+ if (limit - rcp < MAXLINE) {
+ cp = rcp - 1;
+ fillbuf();
+ rcp = ++cp;
+ }
+ assert(cp == rcp);
+ cp = rcp - 1;
+ token = (char *)cp;
+ tsym = fcon();
+ return FCON;
+ case 'L':
+ if (*rcp == '\'') {
+ unsigned int *s = scon(*cp, wcput, wcbuf);
+ if (s - wcbuf > 2)
+ warning("excess characters in wide-character literal ignored\n");
+ tval.type = widechar;
+ tval.u.c.v.u = wcbuf[0];
+ tsym = &tval;
+ return ICON;
+ } else if (*rcp == '"') {
+ unsigned int *s = scon(*cp, wcput, wcbuf);
+ tval.type = array(widechar, s - wcbuf, 0);
+ tval.u.c.v.p = wcbuf;
+ tsym = &tval;
+ return SCON;
+ } else
+ goto id;
+ case '\'': {
+ char *s = scon(*--cp, cput, cbuf);
+ if (s - cbuf > 2)
+ warning("excess characters in multibyte character literal ignored\n");
+ tval.type = inttype;
+ if (chartype->op == INT)
+ tval.u.c.v.i = extend(cbuf[0], chartype);
+ else
+ tval.u.c.v.i = cbuf[0]&0xFF;
+ tsym = &tval;
+ return ICON;
+ }
+ case '"': {
+ char *s = scon(*--cp, cput, cbuf);
+ tval.type = array(chartype, s - cbuf, 0);
+ tval.u.c.v.p = cbuf;
+ tsym = &tval;
+ return SCON;
+ }
+ case 'a':
+ if (rcp[0] == 'u'
+ && rcp[1] == 't'
+ && rcp[2] == 'o'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ return AUTO;
+ }
+ goto id;
+ case 'b':
+ if (rcp[0] == 'r'
+ && rcp[1] == 'e'
+ && rcp[2] == 'a'
+ && rcp[3] == 'k'
+ && !(map[rcp[4]]&(DIGIT|LETTER))) {
+ cp = rcp + 4;
+ return BREAK;
+ }
+ goto id;
+ case 'c':
+ if (rcp[0] == 'a'
+ && rcp[1] == 's'
+ && rcp[2] == 'e'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ return CASE;
+ }
+ if (rcp[0] == 'h'
+ && rcp[1] == 'a'
+ && rcp[2] == 'r'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ tsym = chartype->u.sym;
+ return CHAR;
+ }
+ if (rcp[0] == 'o'
+ && rcp[1] == 'n'
+ && rcp[2] == 's'
+ && rcp[3] == 't'
+ && !(map[rcp[4]]&(DIGIT|LETTER))) {
+ cp = rcp + 4;
+ return CONST;
+ }
+ if (rcp[0] == 'o'
+ && rcp[1] == 'n'
+ && rcp[2] == 't'
+ && rcp[3] == 'i'
+ && rcp[4] == 'n'
+ && rcp[5] == 'u'
+ && rcp[6] == 'e'
+ && !(map[rcp[7]]&(DIGIT|LETTER))) {
+ cp = rcp + 7;
+ return CONTINUE;
+ }
+ goto id;
+ case 'd':
+ if (rcp[0] == 'e'
+ && rcp[1] == 'f'
+ && rcp[2] == 'a'
+ && rcp[3] == 'u'
+ && rcp[4] == 'l'
+ && rcp[5] == 't'
+ && !(map[rcp[6]]&(DIGIT|LETTER))) {
+ cp = rcp + 6;
+ return DEFAULT;
+ }
+ if (rcp[0] == 'o'
+ && rcp[1] == 'u'
+ && rcp[2] == 'b'
+ && rcp[3] == 'l'
+ && rcp[4] == 'e'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ tsym = doubletype->u.sym;
+ return DOUBLE;
+ }
+ if (rcp[0] == 'o'
+ && !(map[rcp[1]]&(DIGIT|LETTER))) {
+ cp = rcp + 1;
+ return DO;
+ }
+ goto id;
+ case 'e':
+ if (rcp[0] == 'l'
+ && rcp[1] == 's'
+ && rcp[2] == 'e'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ return ELSE;
+ }
+ if (rcp[0] == 'n'
+ && rcp[1] == 'u'
+ && rcp[2] == 'm'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ return ENUM;
+ }
+ if (rcp[0] == 'x'
+ && rcp[1] == 't'
+ && rcp[2] == 'e'
+ && rcp[3] == 'r'
+ && rcp[4] == 'n'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ return EXTERN;
+ }
+ goto id;
+ case 'f':
+ if (rcp[0] == 'l'
+ && rcp[1] == 'o'
+ && rcp[2] == 'a'
+ && rcp[3] == 't'
+ && !(map[rcp[4]]&(DIGIT|LETTER))) {
+ cp = rcp + 4;
+ tsym = floattype->u.sym;
+ return FLOAT;
+ }
+ if (rcp[0] == 'o'
+ && rcp[1] == 'r'
+ && !(map[rcp[2]]&(DIGIT|LETTER))) {
+ cp = rcp + 2;
+ return FOR;
+ }
+ goto id;
+ case 'g':
+ if (rcp[0] == 'o'
+ && rcp[1] == 't'
+ && rcp[2] == 'o'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ return GOTO;
+ }
+ goto id;
+ case 'l':
+ if (rcp[0] == 'o'
+ && rcp[1] == 'n'
+ && rcp[2] == 'g'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ return LONG;
+ }
+ goto id;
+ case 'r':
+ if (rcp[0] == 'e'
+ && rcp[1] == 'g'
+ && rcp[2] == 'i'
+ && rcp[3] == 's'
+ && rcp[4] == 't'
+ && rcp[5] == 'e'
+ && rcp[6] == 'r'
+ && !(map[rcp[7]]&(DIGIT|LETTER))) {
+ cp = rcp + 7;
+ return REGISTER;
+ }
+ if (rcp[0] == 'e'
+ && rcp[1] == 't'
+ && rcp[2] == 'u'
+ && rcp[3] == 'r'
+ && rcp[4] == 'n'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ return RETURN;
+ }
+ goto id;
+ case 's':
+ if (rcp[0] == 'h'
+ && rcp[1] == 'o'
+ && rcp[2] == 'r'
+ && rcp[3] == 't'
+ && !(map[rcp[4]]&(DIGIT|LETTER))) {
+ cp = rcp + 4;
+ return SHORT;
+ }
+ if (rcp[0] == 'i'
+ && rcp[1] == 'g'
+ && rcp[2] == 'n'
+ && rcp[3] == 'e'
+ && rcp[4] == 'd'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ return SIGNED;
+ }
+ if (rcp[0] == 'i'
+ && rcp[1] == 'z'
+ && rcp[2] == 'e'
+ && rcp[3] == 'o'
+ && rcp[4] == 'f'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ return SIZEOF;
+ }
+ if (rcp[0] == 't'
+ && rcp[1] == 'a'
+ && rcp[2] == 't'
+ && rcp[3] == 'i'
+ && rcp[4] == 'c'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ return STATIC;
+ }
+ if (rcp[0] == 't'
+ && rcp[1] == 'r'
+ && rcp[2] == 'u'
+ && rcp[3] == 'c'
+ && rcp[4] == 't'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ return STRUCT;
+ }
+ if (rcp[0] == 'w'
+ && rcp[1] == 'i'
+ && rcp[2] == 't'
+ && rcp[3] == 'c'
+ && rcp[4] == 'h'
+ && !(map[rcp[5]]&(DIGIT|LETTER))) {
+ cp = rcp + 5;
+ return SWITCH;
+ }
+ goto id;
+ case 't':
+ if (rcp[0] == 'y'
+ && rcp[1] == 'p'
+ && rcp[2] == 'e'
+ && rcp[3] == 'd'
+ && rcp[4] == 'e'
+ && rcp[5] == 'f'
+ && !(map[rcp[6]]&(DIGIT|LETTER))) {
+ cp = rcp + 6;
+ return TYPEDEF;
+ }
+ goto id;
+ case 'u':
+ if (rcp[0] == 'n'
+ && rcp[1] == 'i'
+ && rcp[2] == 'o'
+ && rcp[3] == 'n'
+ && !(map[rcp[4]]&(DIGIT|LETTER))) {
+ cp = rcp + 4;
+ return UNION;
+ }
+ if (rcp[0] == 'n'
+ && rcp[1] == 's'
+ && rcp[2] == 'i'
+ && rcp[3] == 'g'
+ && rcp[4] == 'n'
+ && rcp[5] == 'e'
+ && rcp[6] == 'd'
+ && !(map[rcp[7]]&(DIGIT|LETTER))) {
+ cp = rcp + 7;
+ return UNSIGNED;
+ }
+ goto id;
+ case 'v':
+ if (rcp[0] == 'o'
+ && rcp[1] == 'i'
+ && rcp[2] == 'd'
+ && !(map[rcp[3]]&(DIGIT|LETTER))) {
+ cp = rcp + 3;
+ tsym = voidtype->u.sym;
+ return VOID;
+ }
+ if (rcp[0] == 'o'
+ && rcp[1] == 'l'
+ && rcp[2] == 'a'
+ && rcp[3] == 't'
+ && rcp[4] == 'i'
+ && rcp[5] == 'l'
+ && rcp[6] == 'e'
+ && !(map[rcp[7]]&(DIGIT|LETTER))) {
+ cp = rcp + 7;
+ return VOLATILE;
+ }
+ goto id;
+ case 'w':
+ if (rcp[0] == 'h'
+ && rcp[1] == 'i'
+ && rcp[2] == 'l'
+ && rcp[3] == 'e'
+ && !(map[rcp[4]]&(DIGIT|LETTER))) {
+ cp = rcp + 4;
+ return WHILE;
+ }
+ goto id;
+ case '_':
+ if (rcp[0] == '_'
+ && rcp[1] == 't'
+ && rcp[2] == 'y'
+ && rcp[3] == 'p'
+ && rcp[4] == 'e'
+ && rcp[5] == 'c'
+ && rcp[6] == 'o'
+ && rcp[7] == 'd'
+ && rcp[8] == 'e'
+ && !(map[rcp[9]]&(DIGIT|LETTER))) {
+ cp = rcp + 9;
+ return TYPECODE;
+ }
+ if (rcp[0] == '_'
+ && rcp[1] == 'f'
+ && rcp[2] == 'i'
+ && rcp[3] == 'r'
+ && rcp[4] == 's'
+ && rcp[5] == 't'
+ && rcp[6] == 'a'
+ && rcp[7] == 'r'
+ && rcp[8] == 'g'
+ && !(map[rcp[9]]&(DIGIT|LETTER))) {
+ cp = rcp + 9;
+ return FIRSTARG;
+ }
+ goto id;
+ default:
+ if ((map[cp[-1]]&BLANK) == 0) {
+ if (cp[-1] < ' ' || cp[-1] >= 0177)
+ error("illegal character `\\0%o'\n", cp[-1]);
+ else
+ error("illegal character `%c'\n", cp[-1]);
+ }
+ }
+ }
+}
+static Symbol icon(unsigned long n, int overflow, int base) {
+ if (((*cp=='u'||*cp=='U') && (cp[1]=='l'||cp[1]=='L'))
+ || ((*cp=='l'||*cp=='L') && (cp[1]=='u'||cp[1]=='U'))) {
+ tval.type = unsignedlong;
+ cp += 2;
+ } else if (*cp == 'u' || *cp == 'U') {
+ if (overflow || n > unsignedtype->u.sym->u.limits.max.i)
+ tval.type = unsignedlong;
+ else
+ tval.type = unsignedtype;
+ cp += 1;
+ } else if (*cp == 'l' || *cp == 'L') {
+ if (overflow || n > longtype->u.sym->u.limits.max.i)
+ tval.type = unsignedlong;
+ else
+ tval.type = longtype;
+ cp += 1;
+ } else if (overflow || n > longtype->u.sym->u.limits.max.i)
+ tval.type = unsignedlong;
+ else if (n > inttype->u.sym->u.limits.max.i)
+ tval.type = longtype;
+ else if (base != 10 && n > inttype->u.sym->u.limits.max.i)
+ tval.type = unsignedtype;
+ else
+ tval.type = inttype;
+ switch (tval.type->op) {
+ case INT:
+ if (overflow || n > tval.type->u.sym->u.limits.max.i) {
+ warning("overflow in constant `%S'\n", token,
+ (char*)cp - token);
+ tval.u.c.v.i = tval.type->u.sym->u.limits.max.i;
+ } else
+ tval.u.c.v.i = n;
+ break;
+ case UNSIGNED:
+ if (overflow || n > tval.type->u.sym->u.limits.max.u) {
+ warning("overflow in constant `%S'\n", token,
+ (char*)cp - token);
+ tval.u.c.v.u = tval.type->u.sym->u.limits.max.u;
+ } else
+ tval.u.c.v.u = n;
+ break;
+ default: assert(0);
+ }
+ ppnumber("integer");
+ return &tval;
+}
+static void ppnumber(char *which) {
+ unsigned char *rcp = cp--;
+
+ for ( ; (map[*cp]&(DIGIT|LETTER)) || *cp == '.'; cp++)
+ if ((cp[0] == 'E' || cp[0] == 'e')
+ && (cp[1] == '-' || cp[1] == '+'))
+ cp++;
+ if (cp > rcp)
+ error("`%S' is a preprocessing number but an invalid %s constant\n", token,
+
+ (char*)cp-token, which);
+}
+static Symbol fcon(void) {
+ if (*cp == '.')
+ do
+ cp++;
+ while (map[*cp]&DIGIT);
+ if (*cp == 'e' || *cp == 'E') {
+ if (*++cp == '-' || *cp == '+')
+ cp++;
+ if (map[*cp]&DIGIT)
+ do
+ cp++;
+ while (map[*cp]&DIGIT);
+ else
+ error("invalid floating constant `%S'\n", token,
+ (char*)cp - token);
+ }
+
+ errno = 0;
+ tval.u.c.v.d = strtod(token, NULL);
+ if (errno == ERANGE)
+ warning("overflow in floating constant `%S'\n", token,
+ (char*)cp - token);
+ if (*cp == 'f' || *cp == 'F') {
+ ++cp;
+ if (tval.u.c.v.d > floattype->u.sym->u.limits.max.d)
+ warning("overflow in floating constant `%S'\n", token,
+ (char*)cp - token);
+ tval.type = floattype;
+ } else if (*cp == 'l' || *cp == 'L') {
+ cp++;
+ tval.type = longdouble;
+ } else {
+ if (tval.u.c.v.d > doubletype->u.sym->u.limits.max.d)
+ warning("overflow in floating constant `%S'\n", token,
+ (char*)cp - token);
+ tval.type = doubletype;
+ }
+ ppnumber("floating");
+ return &tval;
+}
+
+static void *cput(int c, void *cl) {
+ char *s = cl;
+
+ if (c < 0 || c > 255)
+ warning("overflow in escape sequence with resulting value `%d'\n", c);
+ *s++ = c;
+ return s;
+}
+
+static void *wcput(int c, void *cl) {
+ unsigned int *s = cl;
+
+ *s++ = c;
+ return s;
+}
+
+static void *scon(int q, void *put(int c, void *cl), void *cl) {
+ int n = 0, nbad = 0;
+
+ do {
+ cp++;
+ while (*cp != q) {
+ int c;
+ if (map[*cp]&NEWLINE) {
+ if (cp < limit)
+ break;
+ cp++;
+ nextline();
+ if (cp == limit)
+ break;
+ continue;
+ }
+ c = *cp++;
+ if (c == '\\') {
+ if (map[*cp]&NEWLINE) {
+ if (cp < limit)
+ break;
+ cp++;
+ nextline();
+ }
+ if (limit - cp < MAXTOKEN)
+ fillbuf();
+ c = backslash(q);
+ } else if (c < 0 || c > 255 || map[c] == 0)
+ nbad++;
+ if (n++ < BUFSIZE)
+ cl = put(c, cl);
+ }
+ if (*cp == q)
+ cp++;
+ else
+ error("missing %c\n", q);
+ } while (q == '"' && getchr() == '"');
+ cl = put(0, cl);
+ if (n >= BUFSIZE)
+ error("%s literal too long\n", q == '"' ? "string" : "character");
+ if (Aflag >= 2 && q == '"' && n > 509)
+ warning("more than 509 characters in a string literal\n");
+ if (Aflag >= 2 && nbad > 0)
+ warning("%s literal contains non-portable characters\n",
+ q == '"' ? "string" : "character");
+ return cl;
+}
+int getchr(void) {
+ for (;;) {
+ while (map[*cp]&BLANK)
+ cp++;
+ if (!(map[*cp]&NEWLINE))
+ return *cp;
+ cp++;
+ nextline();
+ if (cp == limit)
+ return EOI;
+ }
+}
+static int backslash(int q) {
+ unsigned int c;
+
+ switch (*cp++) {
+ case 'a': return 7;
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case 't': return '\t';
+ case 'v': return '\v';
+ case '\'': case '"': case '\\': case '\?': break;
+ case 'x': {
+ int overflow = 0;
+ if ((map[*cp]&(DIGIT|HEX)) == 0) {
+ if (*cp < ' ' || *cp == 0177)
+ error("ill-formed hexadecimal escape sequence\n");
+ else
+ error("ill-formed hexadecimal escape sequence `\\x%c'\n", *cp);
+ if (*cp != q)
+ cp++;
+ return 0;
+ }
+ for (c = 0; map[*cp]&(DIGIT|HEX); cp++) {
+ if (c >> (8*widechar->size - 4))
+ overflow = 1;
+ if (map[*cp]&DIGIT)
+ c = (c<<4) + *cp - '0';
+ else
+ c = (c<<4) + (*cp&~040) - 'A' + 10;
+ }
+ if (overflow)
+ warning("overflow in hexadecimal escape sequence\n");
+ return c&ones(8*widechar->size);
+ }
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ c = *(cp-1) - '0';
+ if (*cp >= '0' && *cp <= '7') {
+ c = (c<<3) + *cp++ - '0';
+ if (*cp >= '0' && *cp <= '7')
+ c = (c<<3) + *cp++ - '0';
+ }
+ return c;
+ default:
+ if (cp[-1] < ' ' || cp[-1] >= 0177)
+ warning("unrecognized character escape sequence\n");
+ else
+ warning("unrecognized character escape sequence `\\%c'\n", cp[-1]);
+ }
+ return cp[-1];
+}
diff --git a/src/tools/lcc/src/list.c b/src/tools/lcc/src/list.c
new file mode 100644
index 0000000..9c3ec9f
--- /dev/null
+++ b/src/tools/lcc/src/list.c
@@ -0,0 +1,56 @@
+#include "c.h"
+
+
+static List freenodes; /* free list nodes */
+
+/* append - append x to list, return new list */
+List append(void *x, List list) {
+ List new;
+
+ if ((new = freenodes) != NULL)
+ freenodes = freenodes->link;
+ else
+ NEW(new, PERM);
+ if (list) {
+ new->link = list->link;
+ list->link = new;
+ } else
+ new->link = new;
+ new->x = x;
+ return new;
+}
+
+/* length - # elements in list */
+int length(List list) {
+ int n = 0;
+
+ if (list) {
+ List lp = list;
+ do
+ n++;
+ while ((lp = lp->link) != list);
+ }
+ return n;
+}
+
+/* ltov - convert list to a NULL-terminated vector allocated in arena */
+void *ltov(List *list, unsigned arena) {
+ int i = 0;
+ void **array = newarray(length(*list) + 1, sizeof array[0], arena);
+
+ if (*list) {
+ List lp = *list;
+ do {
+ lp = lp->link;
+ array[i++] = lp->x;
+ } while (lp != *list);
+#ifndef PURIFY
+ lp = (*list)->link;
+ (*list)->link = freenodes;
+ freenodes = lp;
+#endif
+ }
+ *list = NULL;
+ array[i] = NULL;
+ return array;
+}
diff --git a/src/tools/lcc/src/main.c b/src/tools/lcc/src/main.c
new file mode 100644
index 0000000..63b85f2
--- /dev/null
+++ b/src/tools/lcc/src/main.c
@@ -0,0 +1,225 @@
+#include "c.h"
+
+static char rcsid[] = "main.c - faked rcsid";
+
+static void typestab(Symbol, void *);
+
+static void stabline(Coordinate *);
+static void stabend(Coordinate *, Symbol, Coordinate **, Symbol *, Symbol *);
+Interface *IR = NULL;
+
+int Aflag; /* >= 0 if -A specified */
+int Pflag; /* != 0 if -P specified */
+int glevel; /* == [0-9] if -g[0-9] specified */
+int xref; /* != 0 for cross-reference data */
+Symbol YYnull; /* _YYnull symbol if -n or -nvalidate specified */
+Symbol YYcheck; /* _YYcheck symbol if -nvalidate,check specified */
+
+static char *comment;
+static Interface stabIR;
+static char *currentfile; /* current file name */
+static int currentline; /* current line number */
+static FILE *srcfp; /* stream for current file, if non-NULL */
+static int srcpos; /* position of srcfp, if srcfp is non-NULL */
+int main(int argc, char *argv[]) {
+ int i, j;
+ for (i = argc - 1; i > 0; i--)
+ if (strncmp(argv[i], "-target=", 8) == 0)
+ break;
+ if (i > 0) {
+ char *s = strchr(argv[i], '\\');
+ if (s != NULL)
+ *s = '/';
+ for (j = 0; bindings[j].name && bindings[j].ir; j++)
+ if (strcmp(&argv[i][8], bindings[j].name) == 0) {
+ IR = bindings[j].ir;
+ break;
+ }
+ if (s != NULL)
+ *s = '\\';
+ }
+ if (!IR) {
+ fprint(stderr, "%s: unknown target", argv[0]);
+ if (i > 0)
+ fprint(stderr, " `%s'", &argv[i][8]);
+ fprint(stderr, "; must specify one of\n");
+ for (i = 0; bindings[i].name; i++)
+ fprint(stderr, "\t-target=%s\n", bindings[i].name);
+ exit(EXIT_FAILURE);
+ }
+ init(argc, argv);
+ t = gettok();
+ (*IR->progbeg)(argc, argv);
+ if (glevel && IR->stabinit)
+ (*IR->stabinit)(firstfile, argc, argv);
+ program();
+ if (events.end)
+ apply(events.end, NULL, NULL);
+ memset(&events, 0, sizeof events);
+ if (glevel || xref) {
+ Symbol symroot = NULL;
+ Coordinate src;
+ foreach(types, GLOBAL, typestab, &symroot);
+ foreach(identifiers, GLOBAL, typestab, &symroot);
+ src.file = firstfile;
+ src.x = 0;
+ src.y = lineno;
+ if ((glevel > 2 || xref) && IR->stabend)
+ (*IR->stabend)(&src, symroot,
+ ltov(&loci, PERM),
+ ltov(&symbols, PERM), NULL);
+ else if (IR->stabend)
+ (*IR->stabend)(&src, NULL, NULL, NULL, NULL);
+ }
+ finalize();
+ (*IR->progend)();
+ deallocate(PERM);
+ return errcnt > 0;
+}
+/* main_init - process program arguments */
+void main_init(int argc, char *argv[]) {
+ char *infile = NULL, *outfile = NULL;
+ int i;
+ static int inited;
+
+ if (inited)
+ return;
+ inited = 1;
+ type_init(argc, argv);
+ for (i = 1; i < argc; i++)
+ if (strcmp(argv[i], "-g") == 0 || strcmp(argv[i], "-g2") == 0)
+ glevel = 2;
+ else if (strncmp(argv[i], "-g", 2) == 0) { /* -gn[,x] */
+ char *p = strchr(argv[i], ',');
+ glevel = atoi(argv[i]+2);
+ if (p) {
+ comment = p + 1;
+ if (glevel == 0)
+ glevel = 1;
+ if (stabIR.stabline == NULL) {
+ stabIR.stabline = IR->stabline;
+ stabIR.stabend = IR->stabend;
+ IR->stabline = stabline;
+ IR->stabend = stabend;
+ }
+ }
+ } else if (strcmp(argv[i], "-x") == 0)
+ xref++;
+ else if (strcmp(argv[i], "-A") == 0) {
+ ++Aflag;
+ } else if (strcmp(argv[i], "-P") == 0)
+ Pflag++;
+ else if (strcmp(argv[i], "-w") == 0)
+ wflag++;
+ else if (strcmp(argv[i], "-n") == 0) {
+ if (!YYnull) {
+ YYnull = install(string("_YYnull"), &globals, GLOBAL, PERM);
+ YYnull->type = func(voidptype, NULL, 1);
+ YYnull->sclass = EXTERN;
+ (*IR->defsymbol)(YYnull);
+ }
+ } else if (strncmp(argv[i], "-n", 2) == 0) { /* -nvalid[,check] */
+ char *p = strchr(argv[i], ',');
+ if (p) {
+ YYcheck = install(string(p+1), &globals, GLOBAL, PERM);
+ YYcheck->type = func(voidptype, NULL, 1);
+ YYcheck->sclass = EXTERN;
+ (*IR->defsymbol)(YYcheck);
+ p = stringn(argv[i]+2, p - (argv[i]+2));
+ } else
+ p = string(argv[i]+2);
+ YYnull = install(p, &globals, GLOBAL, PERM);
+ YYnull->type = func(voidptype, NULL, 1);
+ YYnull->sclass = EXTERN;
+ (*IR->defsymbol)(YYnull);
+ } else if (strcmp(argv[i], "-v") == 0)
+ fprint(stderr, "%s %s\n", argv[0], rcsid);
+ else if (strncmp(argv[i], "-s", 2) == 0)
+ density = strtod(&argv[i][2], NULL);
+ else if (strncmp(argv[i], "-errout=", 8) == 0) {
+ FILE *f = fopen(argv[i]+8, "w");
+ if (f == NULL) {
+ fprint(stderr, "%s: can't write errors to `%s'\n", argv[0], argv[i]+8);
+ exit(EXIT_FAILURE);
+ }
+ fclose(f);
+ f = freopen(argv[i]+8, "w", stderr);
+ assert(f);
+ } else if (strncmp(argv[i], "-e", 2) == 0) {
+ int x;
+ if ((x = strtol(&argv[i][2], NULL, 0)) > 0)
+ errlimit = x;
+ } else if (strncmp(argv[i], "-little_endian=", 15) == 0)
+ IR->little_endian = argv[i][15] - '0';
+ else if (strncmp(argv[i], "-mulops_calls=", 18) == 0)
+ IR->mulops_calls = argv[i][18] - '0';
+ else if (strncmp(argv[i], "-wants_callb=", 13) == 0)
+ IR->wants_callb = argv[i][13] - '0';
+ else if (strncmp(argv[i], "-wants_argb=", 12) == 0)
+ IR->wants_argb = argv[i][12] - '0';
+ else if (strncmp(argv[i], "-left_to_right=", 15) == 0)
+ IR->left_to_right = argv[i][15] - '0';
+ else if (strncmp(argv[i], "-wants_dag=", 11) == 0)
+ IR->wants_dag = argv[i][11] - '0';
+ else if (*argv[i] != '-' || strcmp(argv[i], "-") == 0) {
+ if (infile == NULL)
+ infile = argv[i];
+ else if (outfile == NULL)
+ outfile = argv[i];
+ }
+
+ if (infile != NULL && strcmp(infile, "-") != 0
+ && freopen(infile, "r", stdin) == NULL) {
+ fprint(stderr, "%s: can't read `%s'\n", argv[0], infile);
+ exit(EXIT_FAILURE);
+ }
+ if (outfile != NULL && strcmp(outfile, "-") != 0
+ && freopen(outfile, "w", stdout) == NULL) {
+ fprint(stderr, "%s: can't write `%s'\n", argv[0], outfile);
+ exit(EXIT_FAILURE);
+ }
+}
+/* typestab - emit stab entries for p */
+static void typestab(Symbol p, void *cl) {
+ if (*(Symbol *)cl == 0 && p->sclass && p->sclass != TYPEDEF)
+ *(Symbol *)cl = p;
+ if ((p->sclass == TYPEDEF || p->sclass == 0) && IR->stabtype)
+ (*IR->stabtype)(p);
+}
+
+/* stabline - emit source code for source coordinate *cp */
+static void stabline(Coordinate *cp) {
+ if (cp->file && cp->file != currentfile) {
+ if (srcfp)
+ fclose(srcfp);
+ currentfile = cp->file;
+ srcfp = fopen(currentfile, "r");
+ srcpos = 0;
+ currentline = 0;
+ }
+ if (currentline != cp->y && srcfp) {
+ char buf[512];
+ if (srcpos > cp->y) {
+ rewind(srcfp);
+ srcpos = 0;
+ }
+ for ( ; srcpos < cp->y; srcpos++)
+ if (fgets(buf, sizeof buf, srcfp) == NULL) {
+ fclose(srcfp);
+ srcfp = NULL;
+ break;
+ }
+ if (srcfp && srcpos == cp->y)
+ print("%s%s", comment, buf);
+ }
+ currentline = cp->y;
+ if (stabIR.stabline)
+ (*stabIR.stabline)(cp);
+}
+
+static void stabend(Coordinate *cp, Symbol p, Coordinate **cpp, Symbol *sp, Symbol *stab) {
+ if (stabIR.stabend)
+ (*stabIR.stabend)(cp, p, cpp, sp, stab);
+ if (srcfp)
+ fclose(srcfp);
+}
diff --git a/src/tools/lcc/src/null.c b/src/tools/lcc/src/null.c
new file mode 100644
index 0000000..b9f551c
--- /dev/null
+++ b/src/tools/lcc/src/null.c
@@ -0,0 +1,74 @@
+#include "c.h"
+#define I(f) null_##f
+
+static Node I(gen)(Node p) { return p; }
+static void I(address)(Symbol q, Symbol p, long n) {}
+static void I(blockbeg)(Env *e) {}
+static void I(blockend)(Env *e) {}
+static void I(defaddress)(Symbol p) {}
+static void I(defconst)(int suffix, int size, Value v) {}
+static void I(defstring)(int len, char *s) {}
+static void I(defsymbol)(Symbol p) {}
+static void I(emit)(Node p) {}
+static void I(export)(Symbol p) {}
+static void I(function)(Symbol f, Symbol caller[], Symbol callee[], int ncalls) {}
+static void I(global)(Symbol p) {}
+static void I(import)(Symbol p) {}
+static void I(local)(Symbol p) {}
+static void I(progbeg)(int argc, char *argv[]) {}
+static void I(progend)(void) {}
+static void I(segment)(int s) {}
+static void I(space)(int n) {}
+static void I(stabblock)(int brace, int lev, Symbol *p) {}
+static void I(stabend)(Coordinate *cp, Symbol p, Coordinate **cpp, Symbol *sp, Symbol *stab) {}
+static void I(stabfend)(Symbol p, int lineno) {}
+static void I(stabinit)(char *file, int argc, char *argv[]) {}
+static void I(stabline)(Coordinate *cp) {}
+static void I(stabsym)(Symbol p) {}
+static void I(stabtype)(Symbol p) {}
+
+
+Interface nullIR = {
+ {1, 1, 0}, /* char */
+ {2, 2, 0}, /* short */
+ {4, 4, 0}, /* int */
+ {8, 8, 1}, /* long */
+ {8 ,8, 1}, /* long long */
+ {4, 4, 1}, /* float */
+ {8, 8, 1}, /* double */
+ {16,16,1}, /* long double */
+ {4, 4, 0}, /* T* */
+ {0, 4, 0}, /* struct */
+ 1, /* little_endian */
+ 0, /* mulops_calls */
+ 0, /* wants_callb */
+ 0, /* wants_argb */
+ 1, /* left_to_right */
+ 0, /* wants_dag */
+ 0, /* unsigned_char */
+ I(address),
+ I(blockbeg),
+ I(blockend),
+ I(defaddress),
+ I(defconst),
+ I(defstring),
+ I(defsymbol),
+ I(emit),
+ I(export),
+ I(function),
+ I(gen),
+ I(global),
+ I(import),
+ I(local),
+ I(progbeg),
+ I(progend),
+ I(segment),
+ I(space),
+ I(stabblock),
+ I(stabend),
+ I(stabfend),
+ I(stabinit),
+ I(stabline),
+ I(stabsym),
+ I(stabtype)
+};
diff --git a/src/tools/lcc/src/output.c b/src/tools/lcc/src/output.c
new file mode 100644
index 0000000..a9c93e7
--- /dev/null
+++ b/src/tools/lcc/src/output.c
@@ -0,0 +1,135 @@
+#include "c.h"
+
+
+static char *outs(const char *str, FILE *f, char *bp) {
+ if (f)
+ fputs(str, f);
+ else
+ while ((*bp = *str++))
+ bp++;
+ return bp;
+}
+
+static char *outd(long n, FILE *f, char *bp) {
+ unsigned long m;
+ char buf[25], *s = buf + sizeof buf;
+
+ *--s = '\0';
+ if (n < 0)
+ m = -n;
+ else
+ m = n;
+ do
+ *--s = m%10 + '0';
+ while ((m /= 10) != 0);
+ if (n < 0)
+ *--s = '-';
+ return outs(s, f, bp);
+}
+
+static char *outu(unsigned long n, int base, FILE *f, char *bp) {
+ char buf[25], *s = buf + sizeof buf;
+
+ *--s = '\0';
+ do
+ *--s = "0123456789abcdef"[n%base];
+ while ((n /= base) != 0);
+ return outs(s, f, bp);
+}
+void print(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprint(stdout, NULL, fmt, ap);
+ va_end(ap);
+}
+/* fprint - formatted output to f */
+void fprint(FILE *f, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprint(f, NULL, fmt, ap);
+ va_end(ap);
+}
+
+/* stringf - formatted output to a saved string */
+char *stringf(const char *fmt, ...) {
+ char buf[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprint(NULL, buf, fmt, ap);
+ va_end(ap);
+ return string(buf);
+}
+
+/* vfprint - formatted output to f or string bp */
+void vfprint(FILE *f, char *bp, const char *fmt, va_list ap) {
+ for (; *fmt; fmt++)
+ if (*fmt == '%')
+ switch (*++fmt) {
+ case 'd': bp = outd(va_arg(ap, int), f, bp); break;
+ case 'D': bp = outd(va_arg(ap, long), f, bp); break;
+ case 'U': bp = outu(va_arg(ap, unsigned long), 10, f, bp); break;
+ case 'u': bp = outu(va_arg(ap, unsigned), 10, f, bp); break;
+ case 'o': bp = outu(va_arg(ap, unsigned), 8, f, bp); break;
+ case 'X': bp = outu(va_arg(ap, unsigned long), 16, f, bp); break;
+ case 'x': bp = outu(va_arg(ap, unsigned), 16, f, bp); break;
+ case 'f': case 'e':
+ case 'g': {
+ static char format[] = "%f";
+ char buf[128];
+ format[1] = *fmt;
+ sprintf(buf, format, va_arg(ap, double));
+ bp = outs(buf, f, bp);
+ }
+; break;
+ case 's': bp = outs(va_arg(ap, char *), f, bp); break;
+ case 'p': {
+ void *p = va_arg(ap, void *);
+ if (p)
+ bp = outs("0x", f, bp);
+ bp = outu((unsigned long)p, 16, f, bp);
+ break;
+ }
+ case 'c': if (f) fputc(va_arg(ap, int), f); else *bp++ = va_arg(ap, int); break;
+ case 'S': { char *s = va_arg(ap, char *);
+ int n = va_arg(ap, int);
+ if (s) {
+ for ( ; n-- > 0; s++)
+ if (f) (void)putc(*s, f); else *bp++ = *s;
+ }
+ } break;
+ case 'k': { int t = va_arg(ap, int);
+ static char *tokens[] = {
+#define xx(a,b,c,d,e,f,g) g,
+#define yy(a,b,c,d,e,f,g) g,
+#include "token.h"
+ };
+ assert(tokens[t&0177]);
+ bp = outs(tokens[t&0177], f, bp);
+ } break;
+ case 't': { Type ty = va_arg(ap, Type);
+ assert(f);
+ outtype(ty ? ty : voidtype, f);
+ } break;
+ case 'w': { Coordinate *p = va_arg(ap, Coordinate *);
+ if (p->file && *p->file) {
+ bp = outs(p->file, f, bp);
+ bp = outs(":", f, bp);
+ }
+ bp = outd(p->y, f, bp);
+ } break;
+ case 'I': { int n = va_arg(ap, int);
+ while (--n >= 0)
+ if (f) (void)putc(' ', f); else *bp++ = ' ';
+ } break;
+ default: if (f) (void)putc(*fmt, f); else *bp++ = *fmt; break;
+ }
+ else if (f)
+ (void)putc(*fmt, f);
+ else
+ *bp++ = *fmt;
+ if (!f)
+ *bp = '\0';
+}
diff --git a/src/tools/lcc/src/prof.c b/src/tools/lcc/src/prof.c
new file mode 100644
index 0000000..02709ed
--- /dev/null
+++ b/src/tools/lcc/src/prof.c
@@ -0,0 +1,228 @@
+#include "c.h"
+
+
+struct callsite {
+ char *file, *name;
+ union coordinate {
+ unsigned int coord;
+ struct { unsigned int y:16,x:10,index:6; } le;
+ struct { unsigned int index:6,x:10,y:16; } be;
+ } u;
+};
+struct func {
+ struct func *link;
+ struct caller *callers;
+ char *name;
+ union coordinate src;
+};
+struct map { /* source code map; 200 coordinates/map */
+ int size;
+ union coordinate u[200];
+};
+
+int npoints; /* # of execution points if -b specified */
+int ncalled = -1; /* #times prof.out says current function was called */
+static Symbol YYlink; /* symbol for file's struct _bbdata */
+static Symbol YYcounts; /* symbol for _YYcounts if -b specified */
+static List maplist; /* list of struct map *'s */
+static List filelist; /* list of file names */
+static Symbol funclist; /* list of struct func *'s */
+static Symbol afunc; /* current function's struct func */
+
+/* bbcall - build tree to set _callsite at call site *cp, emit call site data */
+static void bbcall(Symbol yycounts, Coordinate *cp, Tree *e) {
+ static Symbol caller;
+ Value v;
+ union coordinate u;
+ Symbol p = genident(STATIC, array(voidptype, 0, 0), GLOBAL);
+ Tree t = *e;
+
+ defglobal(p, LIT);
+ defpointer(cp->file ? mkstr(cp->file)->u.c.loc : (Symbol)0);
+ defpointer(mkstr(cfunc->name)->u.c.loc);
+ if (IR->little_endian) {
+ u.le.x = cp->x;
+ u.le.y = cp->y;
+ } else {
+ u.be.x = cp->x;
+ u.be.y = cp->y;
+ }
+ (*IR->defconst)(U, unsignedtype->size, (v.u = u.coord, v));
+ if (caller == 0) {
+ caller = mksymbol(EXTERN, "_caller", ptr(voidptype));
+ caller->defined = 0;
+ }
+ if (generic((*e)->op) != CALL)
+ t = (*e)->kids[0];
+ assert(generic(t->op) == CALL);
+ t = tree(t->op, t->type,
+ tree(RIGHT, t->kids[0]->type,
+ t->kids[0],
+ tree(RIGHT, t->kids[0]->type, asgn(caller, idtree(p)), t->kids[0])),
+ t->kids[1]);
+ if (generic((*e)->op) != CALL)
+ t = tree((*e)->op, (*e)->type, t, (*e)->kids[1]);
+ *e = t;
+}
+
+/* bbentry - return tree for _prologue(&afunc, &YYlink)' */
+static void bbentry(Symbol yylink, Symbol f) {
+ static Symbol prologue;
+
+ afunc = genident(STATIC, array(voidptype, 4, 0), GLOBAL);
+ if (prologue == 0) {
+ prologue = mksymbol(EXTERN, "_prologue", ftype(inttype, voidptype));
+ prologue->defined = 0;
+ }
+ walk(vcall(prologue, voidtype, pointer(idtree(afunc)), pointer(idtree(yylink)), NULL), 0, 0);
+
+}
+
+/* bbexit - return tree for _epilogue(&afunc)' */
+static void bbexit(Symbol yylink, Symbol f, Tree e) {
+ static Symbol epilogue;
+
+ if (epilogue == 0) {
+ epilogue = mksymbol(EXTERN, "_epilogue", ftype(inttype, voidptype));
+ epilogue->defined = 0;
+ }
+ walk(vcall(epilogue, voidtype, pointer(idtree(afunc)), NULL), 0, 0);
+}
+
+/* bbfile - add file to list of file names, return its index */
+static int bbfile(char *file) {
+ if (file) {
+ List lp;
+ int i = 1;
+ if ((lp = filelist) != NULL)
+ do {
+ lp = lp->link;
+ if (((Symbol)lp->x)->u.c.v.p == file)
+ return i;
+ i++;
+ } while (lp != filelist);
+ filelist = append(mkstr(file), filelist);
+ return i;
+ }
+ return 0;
+}
+
+/* bbfunc - emit function name and src coordinates */
+static void bbfunc(Symbol yylink, Symbol f) {
+ Value v;
+ union coordinate u;
+
+ defglobal(afunc, DATA);
+ defpointer(funclist);
+ defpointer(NULL);
+ defpointer(mkstr(f->name)->u.c.loc);
+ if (IR->little_endian) {
+ u.le.x = f->u.f.pt.x;
+ u.le.y = f->u.f.pt.y;
+ u.le.index = bbfile(f->u.f.pt.file);
+ } else {
+ u.be.x = f->u.f.pt.x;
+ u.be.y = f->u.f.pt.y;
+ u.be.index = bbfile(f->u.f.pt.file);
+ }
+ (*IR->defconst)(U, unsignedtype->size, (v.u = u.coord, v));
+ funclist = afunc;
+}
+
+/* bbincr - build tree to increment execution point at *cp */
+static void bbincr(Symbol yycounts, Coordinate *cp, Tree *e) {
+ struct map *mp = maplist->x;
+ Tree t;
+
+ /* append *cp to source map */
+ if (mp->size >= NELEMS(mp->u)) {
+ NEW(mp, PERM);
+ mp->size = 0;
+ maplist = append(mp, maplist);
+ }
+ if (IR->little_endian) {
+ mp->u[mp->size].le.x = cp->x;
+ mp->u[mp->size].le.y = cp->y;
+ mp->u[mp->size++].le.index = bbfile(cp->file);
+ } else {
+ mp->u[mp->size].be.x = cp->x;
+ mp->u[mp->size].be.y = cp->y;
+ mp->u[mp->size++].be.index = bbfile(cp->file);
+ }
+ t = incr('+', rvalue((*optree['+'])(ADD, pointer(idtree(yycounts)),
+ consttree(npoints++, inttype))), consttree(1, inttype));
+ if (*e)
+ *e = tree(RIGHT, (*e)->type, t, *e);
+ else
+ *e = t;
+}
+
+/* bbvars - emit definition for basic block counting data */
+static void bbvars(Symbol yylink) {
+ int i, j, n = npoints;
+ Value v;
+ struct map **mp;
+ Symbol coords, files, *p;
+
+ if (!YYcounts && !yylink)
+ return;
+ if (YYcounts) {
+ if (n <= 0)
+ n = 1;
+ YYcounts->type = array(unsignedtype, n, 0);
+ defglobal(YYcounts, BSS);
+ }
+ files = genident(STATIC, array(charptype, 1, 0), GLOBAL);
+ defglobal(files, LIT);
+ for (p = ltov(&filelist, PERM); *p; p++)
+ defpointer((*p)->u.c.loc);
+ defpointer(NULL);
+ coords = genident(STATIC, array(unsignedtype, n, 0), GLOBAL);
+ defglobal(coords, LIT);
+ for (i = n, mp = ltov(&maplist, PERM); *mp; i -= (*mp)->size, mp++)
+ for (j = 0; j < (*mp)->size; j++)
+ (*IR->defconst)(U, unsignedtype->size, (v.u = (*mp)->u[j].coord, v));
+ if (i > 0)
+ (*IR->space)(i*coords->type->type->size);
+ defpointer(NULL);
+ defglobal(yylink, DATA);
+ defpointer(NULL);
+ (*IR->defconst)(U, unsignedtype->size, (v.u = n, v));
+ defpointer(YYcounts);
+ defpointer(coords);
+ defpointer(files);
+ defpointer(funclist);
+}
+
+/* profInit - initialize basic block profiling options */
+void prof_init(int argc, char *argv[]) {
+ int i;
+ static int inited;
+
+ if (inited)
+ return;
+ inited = 1;
+ type_init(argc, argv);
+ if (IR) {
+ for (i = 1; i < argc; i++)
+ if (strncmp(argv[i], "-a", 2) == 0) {
+ if (ncalled == -1
+ && process(argv[i][2] ? &argv[i][2] : "prof.out") > 0)
+ ncalled = 0;
+ } else if ((strcmp(argv[i], "-b") == 0
+ || strcmp(argv[i], "-C") == 0) && YYlink == 0) {
+ YYlink = genident(STATIC, array(unsignedtype, 0, 0), GLOBAL);
+ attach((Apply)bbentry, YYlink, &events.entry);
+ attach((Apply)bbexit, YYlink, &events.returns);
+ attach((Apply)bbfunc, YYlink, &events.exit);
+ attach((Apply)bbvars, YYlink, &events.end);
+ if (strcmp(argv[i], "-b") == 0) {
+ YYcounts = genident(STATIC, array(unsignedtype, 0, 0), GLOBAL);
+ maplist = append(allocate(sizeof (struct map), PERM), maplist);
+ ((struct map *)maplist->x)->size = 0;
+ attach((Apply)bbcall, YYcounts, &events.calls);
+ attach((Apply)bbincr, YYcounts, &events.points);
+ }
+ }
+ }
+}
diff --git a/src/tools/lcc/src/profio.c b/src/tools/lcc/src/profio.c
new file mode 100644
index 0000000..37fc25b
--- /dev/null
+++ b/src/tools/lcc/src/profio.c
@@ -0,0 +1,276 @@
+/* C compiler: prof.out input
+
+prof.out format:
+#files
+ name
+ ... (#files-1 times)
+#functions
+ name file# x y count caller file x y
+ ... (#functions-1 times)
+#points
+ file# x y count
+ ... (#points-1 times)
+*/
+#include "c.h"
+
+
+struct count { /* count data: */
+ int x, y; /* source coordinate */
+ int count; /* associated execution count */
+};
+
+#define MAXTOKEN 64
+
+struct file { /* per-file prof.out data: */
+ struct file *link; /* link to next file */
+ char *name; /* file name */
+ int size; /* size of counts[] */
+ int count; /* counts[0..count-1] hold valid data */
+ struct count *counts; /* count data */
+ struct func { /* function data: */
+ struct func *link; /* link to next function */
+ char *name; /* function name */
+ struct count count; /* total number of calls */
+ struct caller { /* caller data: */
+ struct caller *link; /* link to next caller */
+ char *name; /* caller's name */
+ char *file; /* call site: file, x, y */
+ int x, y;
+ int count; /* number of calls from this site */
+ } *callers;
+ } *funcs; /* list of functions */
+} *filelist;
+FILE *fp;
+
+/* acaller - add caller and site (file,x,y) to callee's callers list */
+static void acaller(char *caller, char *file, int x, int y, int count, struct func *callee) {
+ struct caller *q;
+
+ assert(callee);
+ for (q = callee->callers; q && (caller != q->name
+ || file != q->file || x != q->x || y != q->y); q = q->link)
+ ;
+ if (!q) {
+ struct caller **r;
+ NEW(q, PERM);
+ q->name = caller;
+ q->file = file;
+ q->x = x;
+ q->y = y;
+ q->count = 0;
+ for (r = &callee->callers; *r && (strcmp(q->name, (*r)->name) > 0
+ || strcmp(q->file, (*r)->file) > 0 || q->y > (*r)->y || q->y > (*r)->y); r = &(*r)->link)
+ ;
+ q->link = *r;
+ *r = q;
+ }
+ q->count += count;
+}
+
+/* compare - return <0, 0, >0 if a<b, a==b, a>b, resp. */
+static int compare(struct count *a, struct count *b) {
+ if (a->y == b->y)
+ return a->x - b->x;
+ return a->y - b->y;
+}
+
+/* findfile - return file name's file list entry, or 0 */
+static struct file *findfile(char *name) {
+ struct file *p;
+
+ for (p = filelist; p; p = p->link)
+ if (p->name == name)
+ return p;
+ return 0;
+}
+
+/* afunction - add function name and its data to file's function list */
+static struct func *afunction(char *name, char *file, int x, int y, int count) {
+ struct file *p = findfile(file);
+ struct func *q;
+
+ assert(p);
+ for (q = p->funcs; q && name != q->name; q = q->link)
+ ;
+ if (!q) {
+ struct func **r;
+ NEW(q, PERM);
+ q->name = name;
+ q->count.x = x;
+ q->count.y = y;
+ q->count.count = 0;
+ q->callers = 0;
+ for (r = &p->funcs; *r && compare(&q->count, &(*r)->count) > 0; r = &(*r)->link)
+ ;
+ q->link = *r;
+ *r = q;
+ }
+ q->count.count += count;
+ return q;
+}
+
+/* apoint - append execution point i to file's data */
+static void apoint(int i, char *file, int x, int y, int count) {
+ struct file *p = findfile(file);
+
+ assert(p);
+ if (i >= p->size) {
+ int j;
+ if (p->size == 0) {
+ p->size = i >= 200 ? 2*i : 200;
+ p->counts = newarray(p->size, sizeof *p->counts, PERM);
+ } else {
+ struct count *new;
+ p->size = 2*i;
+ new = newarray(p->size, sizeof *new, PERM);
+ for (j = 0; j < p->count; j++)
+ new[j] = p->counts[j];
+ p->counts = new;
+ }
+ for (j = p->count; j < p->size; j++) {
+ static struct count z;
+ p->counts[j] = z;
+ }
+ }
+ p->counts[i].x = x;
+ p->counts[i].y = y;
+ p->counts[i].count += count;
+ if (i >= p->count)
+ p->count = i + 1;
+}
+
+/* findcount - return count associated with (file,x,y) or -1 */
+int findcount(char *file, int x, int y) {
+ static struct file *cursor;
+
+ if (cursor == 0 || cursor->name != file)
+ cursor = findfile(file);
+ if (cursor) {
+ int l, u;
+ struct count *c = cursor->counts;
+ for (l = 0, u = cursor->count - 1; l <= u; ) {
+ int k = (l + u)/2;
+ if (c[k].y > y || (c[k].y == y && c[k].x > x))
+ u = k - 1;
+ else if (c[k].y < y || (c[k].y == y && c[k].x < x))
+ l = k + 1;
+ else
+ return c[k].count;
+ }
+ }
+ return -1;
+}
+
+/* findfunc - return count associated with function name in file or -1 */
+int findfunc(char *name, char *file) {
+ static struct file *cursor;
+
+ if (cursor == 0 || cursor->name != file)
+ cursor = findfile(file);
+ if (cursor) {
+ struct func *p;
+ for (p = cursor->funcs; p; p = p->link)
+ if (p->name == name)
+ return p->count.count;
+ }
+ return -1;
+}
+
+/* getd - read a nonnegative number */
+static int getd(void) {
+ int c, n = 0;
+
+ while ((c = getc(fp)) != EOF && (c == ' ' || c == '\n' || c == '\t'))
+ ;
+ if (c >= '0' && c <= '9') {
+ do
+ n = 10*n + (c - '0');
+ while ((c = getc(fp)) >= '0' && c <= '9');
+ return n;
+ }
+ return -1;
+}
+
+/* getstr - read a string */
+static char *getstr(void) {
+ int c;
+ char buf[MAXTOKEN], *s = buf;
+
+ while ((c = getc(fp)) != EOF && c != ' ' && c != '\n' && c != '\t')
+ if (s - buf < (int)sizeof buf - 2)
+ *s++ = c;
+ *s = 0;
+ return s == buf ? (char *)0 : string(buf);
+}
+
+/* gather - read prof.out data from fd */
+static int gather(void) {
+ int i, nfiles, nfuncs, npoints;
+ char *files[64];
+
+ if ((nfiles = getd()) < 0)
+ return 0;
+ assert(nfiles < NELEMS(files));
+ for (i = 0; i < nfiles; i++) {
+ if ((files[i] = getstr()) == 0)
+ return -1;
+ if (!findfile(files[i])) {
+ struct file *new;
+ NEW(new, PERM);
+ new->name = files[i];
+ new->size = new->count = 0;
+ new->counts = 0;
+ new->funcs = 0;
+ new->link = filelist;
+ filelist = new;
+ }
+ }
+ if ((nfuncs = getd()) < 0)
+ return -1;
+ for (i = 0; i < nfuncs; i++) {
+ struct func *q;
+ char *name, *file;
+ int f, x, y, count;
+ if ((name = getstr()) == 0 || (f = getd()) <= 0
+ || (x = getd()) < 0 || (y = getd()) < 0 || (count = getd()) < 0)
+ return -1;
+ q = afunction(name, files[f-1], x, y, count);
+ if ((name = getstr()) == 0 || (file = getstr()) == 0
+ || (x = getd()) < 0 || (y = getd()) < 0)
+ return -1;
+ if (*name != '?')
+ acaller(name, file, x, y, count, q);
+ }
+ if ((npoints = getd()) < 0)
+ return -1;
+ for (i = 0; i < npoints; i++) {
+ int f, x, y, count;
+ if ((f = getd()) < 0 || (x = getd()) < 0 || (y = getd()) < 0
+ || (count = getd()) < 0)
+ return -1;
+ if (f)
+ apoint(i, files[f-1], x, y, count);
+ }
+ return 1;
+}
+
+/* process - read prof.out data from file */
+int process(char *file) {
+ int more;
+
+ if ((fp = fopen(file, "r")) != NULL) {
+ struct file *p;
+ while ((more = gather()) > 0)
+ ;
+ fclose(fp);
+ if (more < 0)
+ return more;
+ for (p = filelist; p; p = p->link)
+ qsort(p->counts, p->count, sizeof *p->counts,
+ (int (*)(const void *, const void *))
+ compare);
+
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/tools/lcc/src/simp.c b/src/tools/lcc/src/simp.c
new file mode 100644
index 0000000..ea26ab6
--- /dev/null
+++ b/src/tools/lcc/src/simp.c
@@ -0,0 +1,587 @@
+#include "c.h"
+#include <float.h>
+
+
+#define foldcnst(TYPE,VAR,OP) \
+ if (l->op == CNST+TYPE && r->op == CNST+TYPE) \
+ return cnsttree(ty, l->u.v.VAR OP r->u.v.VAR)
+#define commute(L,R) \
+ if (generic(R->op) == CNST && generic(L->op) != CNST) \
+ do { Tree t = L; L = R; R = t; } while(0)
+#define xfoldcnst(TYPE,VAR,OP,FUNC)\
+ if (l->op == CNST+TYPE && r->op == CNST+TYPE\
+ && FUNC(l->u.v.VAR,r->u.v.VAR,\
+ ty->u.sym->u.limits.min.VAR,\
+ ty->u.sym->u.limits.max.VAR, needconst)) \
+ return cnsttree(ty, l->u.v.VAR OP r->u.v.VAR)
+#define xcvtcnst(FTYPE,SRC,DST,VAR,EXPR) \
+ if (l->op == CNST+FTYPE) do {\
+ if (!explicitCast\
+ && ((SRC) < DST->u.sym->u.limits.min.VAR || (SRC) > DST->u.sym->u.limits.max.VAR))\
+ warning("overflow in converting constant expression from `%t' to `%t'\n", l->type, DST);\
+ if (needconst\
+ || !((SRC) < DST->u.sym->u.limits.min.VAR || (SRC) > DST->u.sym->u.limits.max.VAR))\
+ return cnsttree(ty, (EXPR)); } while(0)
+#define identity(X,Y,TYPE,VAR,VAL) \
+ if (X->op == CNST+TYPE && X->u.v.VAR == VAL) return Y
+#define zerofield(OP,TYPE,VAR) \
+ if (l->op == FIELD \
+ && r->op == CNST+TYPE && r->u.v.VAR == 0)\
+ return eqtree(OP, bittree(BAND, l->kids[0],\
+ cnsttree(unsignedtype, \
+ (unsigned long)fieldmask(l->u.field)<<fieldright(l->u.field))), r)
+#define cfoldcnst(TYPE,VAR,OP) \
+ if (l->op == CNST+TYPE && r->op == CNST+TYPE) \
+ return cnsttree(inttype, (long)(l->u.v.VAR OP r->u.v.VAR))
+#define foldaddp(L,R,RTYPE,VAR) \
+ if (L->op == CNST+P && R->op == CNST+RTYPE) { \
+ Tree e = tree(CNST+P, ty, NULL, NULL);\
+ e->u.v.p = (char *)L->u.v.p + R->u.v.VAR;\
+ return e; }
+#define ufoldcnst(TYPE,EXP) if (l->op == CNST+TYPE) return EXP
+#define sfoldcnst(OP) \
+ if (l->op == CNST+U && r->op == CNST+I \
+ && r->u.v.i >= 0 && r->u.v.i < 8*l->type->size) \
+ return cnsttree(ty, (unsigned long)(l->u.v.u OP r->u.v.i))
+#define geu(L,R,V) \
+ if (R->op == CNST+U && R->u.v.u == 0) do { \
+ warning("result of unsigned comparison is constant\n"); \
+ return tree(RIGHT, inttype, root(L), cnsttree(inttype, (long)(V))); } while(0)
+#define idempotent(OP) if (l->op == OP) return l->kids[0]
+
+int needconst;
+int explicitCast;
+static int addi(long x, long y, long min, long max, int needconst) {
+ int cond = x == 0 || y == 0
+ || (x < 0 && y < 0 && x >= min - y)
+ || (x < 0 && y > 0)
+ || (x > 0 && y < 0)
+ || (x > 0 && y > 0 && x <= max - y);
+ if (!cond && needconst) {
+ warning("overflow in constant expression\n");
+ cond = 1;
+ }
+ return cond;
+
+
+}
+
+static int addd(double x, double y, double min, double max, int needconst) {
+ int cond = x == 0 || y == 0
+ || (x < 0 && y < 0 && x >= min - y)
+ || (x < 0 && y > 0)
+ || (x > 0 && y < 0)
+ || (x > 0 && y > 0 && x <= max - y);
+ if (!cond && needconst) {
+ warning("overflow in constant expression\n");
+ cond = 1;
+ }
+ return cond;
+
+
+}
+
+static Tree addrtree(Tree e, long n, Type ty) {
+ Symbol p = e->u.sym, q;
+
+ if (p->scope == GLOBAL
+ || p->sclass == STATIC || p->sclass == EXTERN)
+ NEW0(q, PERM);
+ else
+ NEW0(q, FUNC);
+ q->name = stringd(genlabel(1));
+ q->sclass = p->sclass;
+ q->scope = p->scope;
+ assert(isptr(ty) || isarray(ty));
+ q->type = isptr(ty) ? ty->type : ty;
+ q->temporary = p->temporary;
+ q->generated = p->generated;
+ q->addressed = p->addressed;
+ q->computed = 1;
+ q->defined = 1;
+ q->ref = 1;
+ if (p->scope == GLOBAL
+ || p->sclass == STATIC || p->sclass == EXTERN) {
+ if (p->sclass == AUTO)
+ q->sclass = STATIC;
+ (*IR->address)(q, p, n);
+ } else {
+ Code cp;
+ addlocal(p);
+ cp = code(Address);
+ cp->u.addr.sym = q;
+ cp->u.addr.base = p;
+ cp->u.addr.offset = n;
+ }
+ e = tree(e->op, ty, NULL, NULL);
+ e->u.sym = q;
+ return e;
+}
+
+/* div[id] - return 1 if min <= x/y <= max, 0 otherwise */
+static int divi(long x, long y, long min, long max, int needconst) {
+ int cond = y != 0 && !(x == min && y == -1);
+ if (!cond && needconst) {
+ warning("overflow in constant expression\n");
+ cond = 1;
+ }
+ return cond;
+
+
+}
+
+static int divd(double x, double y, double min, double max, int needconst) {
+ int cond;
+
+ if (x < 0) x = -x;
+ if (y < 0) y = -y;
+ cond = y != 0 && !(y < 1 && x > max*y);
+ if (!cond && needconst) {
+ warning("overflow in constant expression\n");
+ cond = 1;
+ }
+ return cond;
+
+}
+
+/* mul[id] - return 1 if min <= x*y <= max, 0 otherwise */
+static int muli(long x, long y, long min, long max, int needconst) {
+ int cond = (x > -1 && x <= 1) || (y > -1 && y <= 1)
+ || (x < 0 && y < 0 && -x <= max/-y)
+ || (x < 0 && y > 0 && x >= min/y)
+ || (x > 0 && y < 0 && y >= min/x)
+ || (x > 0 && y > 0 && x <= max/y);
+ if (!cond && needconst) {
+ warning("overflow in constant expression\n");
+ cond = 1;
+ }
+ return cond;
+
+
+}
+
+static int muld(double x, double y, double min, double max, int needconst) {
+ int cond = (x >= -1 && x <= 1) || (y >= -1 && y <= 1)
+ || (x < 0 && y < 0 && -x <= max/-y)
+ || (x < 0 && y > 0 && x >= min/y)
+ || (x > 0 && y < 0 && y >= min/x)
+ || (x > 0 && y > 0 && x <= max/y);
+ if (!cond && needconst) {
+ warning("overflow in constant expression\n");
+ cond = 1;
+ }
+ return cond;
+
+
+}
+/* sub[id] - return 1 if min <= x-y <= max, 0 otherwise */
+static int subi(long x, long y, long min, long max, int needconst) {
+ return addi(x, -y, min, max, needconst);
+}
+
+static int subd(double x, double y, double min, double max, int needconst) {
+ return addd(x, -y, min, max, needconst);
+}
+Tree constexpr(int tok) {
+ Tree p;
+
+ needconst++;
+ p = expr1(tok);
+ needconst--;
+ return p;
+}
+
+int intexpr(int tok, int n) {
+ Tree p = constexpr(tok);
+
+ needconst++;
+ if (p->op == CNST+I || p->op == CNST+U)
+ n = cast(p, inttype)->u.v.i;
+ else
+ error("integer expression must be constant\n");
+ needconst--;
+ return n;
+}
+Tree simplify(int op, Type ty, Tree l, Tree r) {
+ int n;
+
+ if (optype(op) == 0)
+ op = mkop(op, ty);
+ switch (op) {
+ case ADD+U:
+ foldcnst(U,u,+);
+ commute(r,l);
+ identity(r,l,U,u,0);
+ break;
+ case ADD+I:
+ xfoldcnst(I,i,+,addi);
+ commute(r,l);
+ identity(r,l,I,i,0);
+ break;
+ case CVI+I:
+ xcvtcnst(I,l->u.v.i,ty,i,(long)extend(l->u.v.i,ty));
+ break;
+ case CVU+I:
+ if (l->op == CNST+U) {
+ if (!explicitCast && l->u.v.u > ty->u.sym->u.limits.max.i)
+ warning("overflow in converting constant expression from `%t' to `%t'\n", l->type, ty);
+ if (needconst || !(l->u.v.u > ty->u.sym->u.limits.max.i))
+ return cnsttree(ty, (long)extend(l->u.v.u,ty));
+ }
+ break;
+ case CVP+U:
+ xcvtcnst(P,(unsigned long)l->u.v.p,ty,u,(unsigned long)l->u.v.p);
+ break;
+ case CVU+P:
+ xcvtcnst(U,(void*)l->u.v.u,ty,p,(void*)l->u.v.u);
+ break;
+ case CVP+P:
+ xcvtcnst(P,l->u.v.p,ty,p,l->u.v.p);
+ break;
+ case CVI+U:
+ xcvtcnst(I,l->u.v.i,ty,u,((unsigned long)l->u.v.i)&ones(8*ty->size));
+ break;
+ case CVU+U:
+ xcvtcnst(U,l->u.v.u,ty,u,l->u.v.u&ones(8*ty->size));
+ break;
+
+ case CVI+F:
+ xcvtcnst(I,l->u.v.i,ty,d,(double)l->u.v.i);
+ case CVU+F:
+ xcvtcnst(U,l->u.v.u,ty,d,(double)l->u.v.u);
+ break;
+ case CVF+I:
+ xcvtcnst(F,l->u.v.d,ty,i,(long)l->u.v.d);
+ break;
+ case CVF+F: {
+ float d = 0.0f;
+ if (l->op == CNST+F) {
+ if (l->u.v.d < ty->u.sym->u.limits.min.d)
+ d = ty->u.sym->u.limits.min.d;
+ else if (l->u.v.d > ty->u.sym->u.limits.max.d)
+ d = ty->u.sym->u.limits.max.d;
+ else
+ d = l->u.v.d;
+ }
+ xcvtcnst(F,l->u.v.d,ty,d,(double)d);
+ break;
+ }
+ case BAND+U:
+ foldcnst(U,u,&);
+ commute(r,l);
+ identity(r,l,U,u,ones(8*ty->size));
+ if (r->op == CNST+U && r->u.v.u == 0)
+ return tree(RIGHT, ty, root(l), cnsttree(ty, 0UL));
+ break;
+ case BAND+I:
+ foldcnst(I,i,&);
+ commute(r,l);
+ identity(r,l,I,i,ones(8*ty->size));
+ if (r->op == CNST+I && r->u.v.u == 0)
+ return tree(RIGHT, ty, root(l), cnsttree(ty, 0L));
+ break;
+
+ case MUL+U:
+ commute(l,r);
+ if (l->op == CNST+U && (n = ispow2(l->u.v.u)) != 0)
+ return simplify(LSH, ty, r, cnsttree(inttype, (long)n));
+ foldcnst(U,u,*);
+ identity(r,l,U,u,1);
+ break;
+ case NE+I:
+ cfoldcnst(I,i,!=);
+ commute(r,l);
+ zerofield(NE,I,i);
+ break;
+
+ case EQ+I:
+ cfoldcnst(I,i,==);
+ commute(r,l);
+ zerofield(EQ,I,i);
+ break;
+ case ADD+P:
+ foldaddp(l,r,I,i);
+ foldaddp(l,r,U,u);
+ foldaddp(r,l,I,i);
+ foldaddp(r,l,U,u);
+ commute(r,l);
+ identity(r,retype(l,ty),I,i,0);
+ identity(r,retype(l,ty),U,u,0);
+ if (isaddrop(l->op)
+ && ((r->op == CNST+I && r->u.v.i <= longtype->u.sym->u.limits.max.i
+ && r->u.v.i >= longtype->u.sym->u.limits.min.i)
+ || (r->op == CNST+U && r->u.v.u <= longtype->u.sym->u.limits.max.i)))
+ return addrtree(l, cast(r, longtype)->u.v.i, ty);
+ if (l->op == ADD+P && isaddrop(l->kids[1]->op)
+ && ((r->op == CNST+I && r->u.v.i <= longtype->u.sym->u.limits.max.i
+ && r->u.v.i >= longtype->u.sym->u.limits.min.i)
+ || (r->op == CNST+U && r->u.v.u <= longtype->u.sym->u.limits.max.i)))
+ return simplify(ADD+P, ty, l->kids[0],
+ addrtree(l->kids[1], cast(r, longtype)->u.v.i, ty));
+ if ((l->op == ADD+I || l->op == SUB+I)
+ && l->kids[1]->op == CNST+I && isaddrop(r->op))
+ return simplify(ADD+P, ty, l->kids[0],
+ simplify(generic(l->op)+P, ty, r, l->kids[1]));
+ if (l->op == ADD+P && generic(l->kids[1]->op) == CNST
+ && generic(r->op) == CNST)
+ return simplify(ADD+P, ty, l->kids[0],
+ simplify(ADD, l->kids[1]->type, l->kids[1], r));
+ if (l->op == ADD+I && generic(l->kids[1]->op) == CNST
+ && r->op == ADD+P && generic(r->kids[1]->op) == CNST)
+ return simplify(ADD+P, ty, l->kids[0],
+ simplify(ADD+P, ty, r->kids[0],
+ simplify(ADD, r->kids[1]->type, l->kids[1], r->kids[1])));
+ if (l->op == RIGHT && l->kids[1])
+ return tree(RIGHT, ty, l->kids[0],
+ simplify(ADD+P, ty, l->kids[1], r));
+ else if (l->op == RIGHT && l->kids[0])
+ return tree(RIGHT, ty,
+ simplify(ADD+P, ty, l->kids[0], r), NULL);
+ break;
+
+ case ADD+F:
+ xfoldcnst(F,d,+,addd);
+ commute(r,l);
+ break;
+ case AND+I:
+ op = AND;
+ ufoldcnst(I,l->u.v.i ? cond(r) : l); /* 0&&r => 0, 1&&r => r */
+ break;
+ case OR+I:
+ op = OR;
+ /* 0||r => r, 1||r => 1 */
+ ufoldcnst(I,l->u.v.i ? cnsttree(ty, 1L) : cond(r));
+ break;
+ case BCOM+I:
+ ufoldcnst(I,cnsttree(ty, (long)extend((~l->u.v.i)&ones(8*ty->size), ty)));
+ idempotent(BCOM+U);
+ break;
+ case BCOM+U:
+ ufoldcnst(U,cnsttree(ty, (unsigned long)((~l->u.v.u)&ones(8*ty->size))));
+ idempotent(BCOM+U);
+ break;
+ case BOR+U:
+ foldcnst(U,u,|);
+ commute(r,l);
+ identity(r,l,U,u,0);
+ break;
+ case BOR+I:
+ foldcnst(I,i,|);
+ commute(r,l);
+ identity(r,l,I,i,0);
+ break;
+ case BXOR+U:
+ foldcnst(U,u,^);
+ commute(r,l);
+ identity(r,l,U,u,0);
+ break;
+ case BXOR+I:
+ foldcnst(I,i,^);
+ commute(r,l);
+ identity(r,l,I,i,0);
+ break;
+ case DIV+F:
+ xfoldcnst(F,d,/,divd);
+ break;
+ case DIV+I:
+ identity(r,l,I,i,1);
+ if ((r->op == CNST+I && r->u.v.i == 0)
+ || (l->op == CNST+I && l->u.v.i == ty->u.sym->u.limits.min.i
+ && r->op == CNST+I && r->u.v.i == -1))
+ break;
+ xfoldcnst(I,i,/,divi);
+ break;
+ case DIV+U:
+ identity(r,l,U,u,1);
+ if (r->op == CNST+U && r->u.v.u == 0)
+ break;
+ if (r->op == CNST+U && (n = ispow2(r->u.v.u)) != 0)
+ return simplify(RSH, ty, l, cnsttree(inttype, (long)n));
+ foldcnst(U,u,/);
+ break;
+ case EQ+F:
+ cfoldcnst(F,d,==);
+ commute(r,l);
+ break;
+ case EQ+U:
+ cfoldcnst(U,u,==);
+ commute(r,l);
+ zerofield(EQ,U,u);
+ break;
+ case GE+F: cfoldcnst(F,d,>=); break;
+ case GE+I: cfoldcnst(I,i,>=); break;
+ case GE+U:
+ geu(l,r,1); /* l >= 0 => (l,1) */
+ cfoldcnst(U,u,>=);
+ if (l->op == CNST+U && l->u.v.u == 0) /* 0 >= r => r == 0 */
+ return eqtree(EQ, r, l);
+ break;
+ case GT+F: cfoldcnst(F,d, >); break;
+ case GT+I: cfoldcnst(I,i, >); break;
+ case GT+U:
+ geu(r,l,0); /* 0 > r => (r,0) */
+ cfoldcnst(U,u, >);
+ if (r->op == CNST+U && r->u.v.u == 0) /* l > 0 => l != 0 */
+ return eqtree(NE, l, r);
+ break;
+ case LE+F: cfoldcnst(F,d,<=); break;
+ case LE+I: cfoldcnst(I,i,<=); break;
+ case LE+U:
+ geu(r,l,1); /* 0 <= r => (r,1) */
+ cfoldcnst(U,u,<=);
+ if (r->op == CNST+U && r->u.v.u == 0) /* l <= 0 => l == 0 */
+ return eqtree(EQ, l, r);
+ break;
+ case LSH+I:
+ identity(r,l,I,i,0);
+ if (l->op == CNST+I && r->op == CNST+I
+ && r->u.v.i >= 0 && r->u.v.i < 8*l->type->size
+ && muli(l->u.v.i, 1<<r->u.v.i, ty->u.sym->u.limits.min.i, ty->u.sym->u.limits.max.i, needconst))
+ return cnsttree(ty, (long)(l->u.v.i<<r->u.v.i));
+ if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) {
+ warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i);
+ break;
+ }
+
+ break;
+ case LSH+U:
+ identity(r,l,I,i,0);
+ sfoldcnst(<<);
+ if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) {
+ warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i);
+ break;
+ }
+
+ break;
+
+ case LT+F: cfoldcnst(F,d, <); break;
+ case LT+I: cfoldcnst(I,i, <); break;
+ case LT+U:
+ geu(l,r,0); /* l < 0 => (l,0) */
+ cfoldcnst(U,u, <);
+ if (l->op == CNST+U && l->u.v.u == 0) /* 0 < r => r != 0 */
+ return eqtree(NE, r, l);
+ break;
+ case MOD+I:
+ if (r->op == CNST+I && r->u.v.i == 1) /* l%1 => (l,0) */
+ return tree(RIGHT, ty, root(l), cnsttree(ty, 0L));
+ if ((r->op == CNST+I && r->u.v.i == 0)
+ || (l->op == CNST+I && l->u.v.i == ty->u.sym->u.limits.min.i
+ && r->op == CNST+I && r->u.v.i == -1))
+ break;
+ xfoldcnst(I,i,%,divi);
+ break;
+ case MOD+U:
+ if (r->op == CNST+U && ispow2(r->u.v.u)) /* l%2^n => l&(2^n-1) */
+ return bittree(BAND, l, cnsttree(ty, r->u.v.u - 1));
+ if (r->op == CNST+U && r->u.v.u == 0)
+ break;
+ foldcnst(U,u,%);
+ break;
+ case MUL+F:
+ xfoldcnst(F,d,*,muld);
+ commute(l,r);
+ break;
+ case MUL+I:
+ commute(l,r);
+ xfoldcnst(I,i,*,muli);
+ if (l->op == CNST+I && r->op == ADD+I && r->kids[1]->op == CNST+I)
+ /* c1*(x + c2) => c1*x + c1*c2 */
+ return simplify(ADD, ty, simplify(MUL, ty, l, r->kids[0]),
+ simplify(MUL, ty, l, r->kids[1]));
+ if (l->op == CNST+I && r->op == SUB+I && r->kids[1]->op == CNST+I)
+ /* c1*(x - c2) => c1*x - c1*c2 */
+ return simplify(SUB, ty, simplify(MUL, ty, l, r->kids[0]),
+ simplify(MUL, ty, l, r->kids[1]));
+ if (l->op == CNST+I && l->u.v.i > 0 && (n = ispow2(l->u.v.i)) != 0)
+ /* 2^n * r => r<<n */
+ return simplify(LSH, ty, r, cnsttree(inttype, (long)n));
+ identity(r,l,I,i,1);
+ break;
+ case NE+F:
+ cfoldcnst(F,d,!=);
+ commute(r,l);
+ break;
+ case NE+U:
+ cfoldcnst(U,u,!=);
+ commute(r,l);
+ zerofield(NE,U,u);
+ break;
+ case NEG+F:
+ ufoldcnst(F,cnsttree(ty, -l->u.v.d));
+ idempotent(NEG+F);
+ break;
+ case NEG+I:
+ if (l->op == CNST+I) {
+ if (needconst && l->u.v.i == ty->u.sym->u.limits.min.i)
+ warning("overflow in constant expression\n");
+ if (needconst || l->u.v.i != ty->u.sym->u.limits.min.i)
+ return cnsttree(ty, -l->u.v.i);
+ }
+ idempotent(NEG+I);
+ break;
+ case NOT+I:
+ op = NOT;
+ ufoldcnst(I,cnsttree(ty, !l->u.v.i));
+ break;
+ case RSH+I:
+ identity(r,l,I,i,0);
+ if (l->op == CNST+I && r->op == CNST+I
+ && r->u.v.i >= 0 && r->u.v.i < 8*l->type->size) {
+ long n = l->u.v.i>>r->u.v.i;
+ if (l->u.v.i < 0)
+ n |= ~0UL<<(8*l->type->size - r->u.v.i);
+ return cnsttree(ty, n);
+ }
+ if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) {
+ warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i);
+ break;
+ }
+
+ break;
+ case RSH+U:
+ identity(r,l,I,i,0);
+ sfoldcnst(>>);
+ if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) {
+ warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i);
+ break;
+ }
+
+ break;
+ case SUB+F:
+ xfoldcnst(F,d,-,subd);
+ break;
+ case SUB+I:
+ xfoldcnst(I,i,-,subi);
+ identity(r,l,I,i,0);
+ break;
+ case SUB+U:
+ foldcnst(U,u,-);
+ identity(r,l,U,u,0);
+ break;
+ case SUB+P:
+ if (l->op == CNST+P && r->op == CNST+P)
+ return cnsttree(ty, (long)((char *)l->u.v.p - (char *)r->u.v.p));
+ if (r->op == CNST+I || r->op == CNST+U)
+ return simplify(ADD, ty, l,
+ cnsttree(inttype, r->op == CNST+I ? -r->u.v.i : -(long)r->u.v.u));
+ if (isaddrop(l->op) && r->op == ADD+I && r->kids[1]->op == CNST+I)
+ /* l - (x + c) => l-c - x */
+ return simplify(SUB, ty,
+ simplify(SUB, ty, l, r->kids[1]), r->kids[0]);
+ break;
+ default:assert(0);
+ }
+ return tree(op, ty, l, r);
+}
+/* ispow2 - if u > 1 && u == 2^n, return n, otherwise return 0 */
+int ispow2(unsigned long u) {
+ int n;
+
+ if (u > 1 && (u&(u-1)) == 0)
+ for (n = 0; u; u >>= 1, n++)
+ if (u&1)
+ return n;
+ return 0;
+}
+
diff --git a/src/tools/lcc/src/stmt.c b/src/tools/lcc/src/stmt.c
new file mode 100644
index 0000000..9c3bdbe
--- /dev/null
+++ b/src/tools/lcc/src/stmt.c
@@ -0,0 +1,696 @@
+#include "c.h"
+
+
+#define SWSIZE 512
+
+#define den(i,j) ((j-buckets[i]+1.0)/(v[j]-v[buckets[i]]+1))
+
+struct code codehead = { Start };
+Code codelist = &codehead;
+float density = 0.5;
+Table stmtlabs;
+
+static int foldcond(Tree e1, Tree e2);
+static void caselabel(Swtch, long, int);
+static void cmp(int, Symbol, long, int);
+static Tree conditional(int);
+static void dostmt(int, Swtch, int);
+static int equal(Symbol, Symbol);
+static void forstmt(int, Swtch, int);
+static void ifstmt(int, int, Swtch, int);
+static Symbol localaddr(Tree);
+static void stmtlabel(void);
+static void swstmt(int, int, int);
+static void whilestmt(int, Swtch, int);
+Code code(int kind) {
+ Code cp;
+
+ if (!reachable(kind))
+ warning("unreachable code\n");
+
+ NEW(cp, FUNC);
+ cp->kind = kind;
+ cp->prev = codelist;
+ cp->next = NULL;
+ codelist->next = cp;
+ codelist = cp;
+ return cp;
+}
+int reachable(int kind) {
+
+ if (kind > Start) {
+ Code cp;
+ for (cp = codelist; cp->kind < Label; )
+ cp = cp->prev;
+ if (cp->kind == Jump || cp->kind == Switch)
+ return 0;
+ }
+ return 1;
+}
+void addlocal(Symbol p) {
+ if (!p->defined) {
+ code(Local)->u.var = p;
+ p->defined = 1;
+ p->scope = level;
+ }
+}
+void definept(Coordinate *p) {
+ Code cp = code(Defpoint);
+
+ cp->u.point.src = p ? *p : src;
+ cp->u.point.point = npoints;
+ if (ncalled > 0) {
+ int n = findcount(cp->u.point.src.file,
+ cp->u.point.src.x, cp->u.point.src.y);
+ if (n > 0)
+ refinc = (float)n/ncalled;
+ }
+ if (glevel > 2) locus(identifiers, &cp->u.point.src);
+ if (events.points && reachable(Gen))
+ {
+ Tree e = NULL;
+ apply(events.points, &cp->u.point.src, &e);
+ if (e)
+ listnodes(e, 0, 0);
+ }
+}
+void statement(int loop, Swtch swp, int lev) {
+ float ref = refinc;
+
+ if (Aflag >= 2 && lev == 15)
+ warning("more than 15 levels of nested statements\n");
+ switch (t) {
+ case IF: ifstmt(genlabel(2), loop, swp, lev + 1);
+ break;
+ case WHILE: whilestmt(genlabel(3), swp, lev + 1); break;
+ case DO: dostmt(genlabel(3), swp, lev + 1); expect(';');
+ break;
+
+ case FOR: forstmt(genlabel(4), swp, lev + 1);
+ break;
+ case BREAK: walk(NULL, 0, 0);
+ definept(NULL);
+ if (swp && swp->lab > loop)
+ branch(swp->lab + 1);
+ else if (loop)
+ branch(loop + 2);
+ else
+ error("illegal break statement\n");
+ t = gettok(); expect(';');
+ break;
+
+ case CONTINUE: walk(NULL, 0, 0);
+ definept(NULL);
+ if (loop)
+ branch(loop + 1);
+ else
+ error("illegal continue statement\n");
+ t = gettok(); expect(';');
+ break;
+
+ case SWITCH: swstmt(loop, genlabel(2), lev + 1);
+ break;
+ case CASE: {
+ int lab = genlabel(1);
+ if (swp == NULL)
+ error("illegal case label\n");
+ definelab(lab);
+ while (t == CASE) {
+ static char stop[] = { IF, ID, 0 };
+ Tree p;
+ t = gettok();
+ p = constexpr(0);
+ if (generic(p->op) == CNST && isint(p->type)) {
+ if (swp) {
+ needconst++;
+ p = cast(p, swp->sym->type);
+ if (p->type->op == UNSIGNED)
+ p->u.v.i = extend(p->u.v.u, p->type);
+ needconst--;
+ caselabel(swp, p->u.v.i, lab);
+ }
+ } else
+ error("case label must be a constant integer expression\n");
+
+ test(':', stop);
+ }
+ statement(loop, swp, lev);
+ } break;
+ case DEFAULT: if (swp == NULL)
+ error("illegal default label\n");
+ else if (swp->deflab)
+ error("extra default label\n");
+ else {
+ swp->deflab = findlabel(swp->lab);
+ definelab(swp->deflab->u.l.label);
+ }
+ t = gettok();
+ expect(':');
+ statement(loop, swp, lev); break;
+ case RETURN: {
+ Type rty = freturn(cfunc->type);
+ t = gettok();
+ definept(NULL);
+ if (t != ';')
+ if (rty == voidtype) {
+ error("extraneous return value\n");
+ expr(0);
+ retcode(NULL);
+ } else
+ retcode(expr(0));
+ else {
+ if (rty != voidtype)
+ warning("missing return value\n");
+ retcode(NULL);
+ }
+ branch(cfunc->u.f.label);
+ } expect(';');
+ break;
+
+ case '{': compound(loop, swp, lev + 1); break;
+ case ';': definept(NULL); t = gettok(); break;
+ case GOTO: walk(NULL, 0, 0);
+ definept(NULL);
+ t = gettok();
+ if (t == ID) {
+ Symbol p = lookup(token, stmtlabs);
+ if (p == NULL) {
+ p = install(token, &stmtlabs, 0, FUNC);
+ p->scope = LABELS;
+ p->u.l.label = genlabel(1);
+ p->src = src;
+ }
+ use(p, src);
+ branch(p->u.l.label);
+ t = gettok();
+ } else
+ error("missing label in goto\n"); expect(';');
+ break;
+
+ case ID: if (getchr() == ':') {
+ stmtlabel();
+ statement(loop, swp, lev);
+ break;
+ }
+ default: definept(NULL);
+ if (kind[t] != ID) {
+ error("unrecognized statement\n");
+ t = gettok();
+ } else {
+ Tree e = expr0(0);
+ listnodes(e, 0, 0);
+ if (nodecount == 0 || nodecount > 200)
+ walk(NULL, 0, 0);
+ else if (glevel) walk(NULL, 0, 0);
+ deallocate(STMT);
+ } expect(';');
+ break;
+
+ }
+ if (kind[t] != IF && kind[t] != ID
+ && t != '}' && t != EOI) {
+ static char stop[] = { IF, ID, '}', 0 };
+ error("illegal statement termination\n");
+ skipto(0, stop);
+ }
+ refinc = ref;
+}
+
+static void ifstmt(int lab, int loop, Swtch swp, int lev) {
+ t = gettok();
+ expect('(');
+ definept(NULL);
+ walk(conditional(')'), 0, lab);
+ refinc /= 2.0;
+ statement(loop, swp, lev);
+ if (t == ELSE) {
+ branch(lab + 1);
+ t = gettok();
+ definelab(lab);
+ statement(loop, swp, lev);
+ if (findlabel(lab + 1)->ref)
+ definelab(lab + 1);
+ } else
+ definelab(lab);
+}
+static Tree conditional(int tok) {
+ Tree p = expr(tok);
+
+ if (Aflag > 1 && isfunc(p->type))
+ warning("%s used in a conditional expression\n",
+ funcname(p));
+ return cond(p);
+}
+static void stmtlabel(void) {
+ Symbol p = lookup(token, stmtlabs);
+
+ if (p == NULL) {
+ p = install(token, &stmtlabs, 0, FUNC);
+ p->scope = LABELS;
+ p->u.l.label = genlabel(1);
+ p->src = src;
+ }
+ if (p->defined)
+ error("redefinition of label `%s' previously defined at %w\n", p->name, &p->src);
+
+ p->defined = 1;
+ definelab(p->u.l.label);
+ t = gettok();
+ expect(':');
+}
+static void forstmt(int lab, Swtch swp, int lev) {
+ int once = 0;
+ Tree e1 = NULL, e2 = NULL, e3 = NULL;
+ Coordinate pt2, pt3;
+
+ t = gettok();
+ expect('(');
+ definept(NULL);
+ if (kind[t] == ID)
+ e1 = texpr(expr0, ';', FUNC);
+ else
+ expect(';');
+ walk(e1, 0, 0);
+ pt2 = src;
+ refinc *= 10.0;
+ if (kind[t] == ID)
+ e2 = texpr(conditional, ';', FUNC);
+ else
+ expect(';');
+ pt3 = src;
+ if (kind[t] == ID)
+ e3 = texpr(expr0, ')', FUNC);
+ else {
+ static char stop[] = { IF, ID, '}', 0 };
+ test(')', stop);
+ }
+ if (e2) {
+ once = foldcond(e1, e2);
+ if (!once)
+ branch(lab + 3);
+ }
+ definelab(lab);
+ statement(lab, swp, lev);
+ definelab(lab + 1);
+ definept(&pt3);
+ if (e3)
+ walk(e3, 0, 0);
+ if (e2) {
+ if (!once)
+ definelab(lab + 3);
+ definept(&pt2);
+ walk(e2, lab, 0);
+ } else {
+ definept(&pt2);
+ branch(lab);
+ }
+ if (findlabel(lab + 2)->ref)
+ definelab(lab + 2);
+}
+static void swstmt(int loop, int lab, int lev) {
+ Tree e;
+ struct swtch sw;
+ Code head, tail;
+
+ t = gettok();
+ expect('(');
+ definept(NULL);
+ e = expr(')');
+ if (!isint(e->type)) {
+ error("illegal type `%t' in switch expression\n",
+ e->type);
+ e = retype(e, inttype);
+ }
+ e = cast(e, promote(e->type));
+ if (generic(e->op) == INDIR && isaddrop(e->kids[0]->op)
+ && e->kids[0]->u.sym->type == e->type
+ && !isvolatile(e->kids[0]->u.sym->type)) {
+ sw.sym = e->kids[0]->u.sym;
+ walk(NULL, 0, 0);
+ } else {
+ sw.sym = genident(REGISTER, e->type, level);
+ addlocal(sw.sym);
+ walk(asgn(sw.sym, e), 0, 0);
+ }
+ head = code(Switch);
+ sw.lab = lab;
+ sw.deflab = NULL;
+ sw.ncases = 0;
+ sw.size = SWSIZE;
+ sw.values = newarray(SWSIZE, sizeof *sw.values, FUNC);
+ sw.labels = newarray(SWSIZE, sizeof *sw.labels, FUNC);
+ refinc /= 10.0;
+ statement(loop, &sw, lev);
+ if (sw.deflab == NULL) {
+ sw.deflab = findlabel(lab);
+ definelab(lab);
+ if (sw.ncases == 0)
+ warning("switch statement with no cases\n");
+ }
+ if (findlabel(lab + 1)->ref)
+ definelab(lab + 1);
+ tail = codelist;
+ codelist = head->prev;
+ codelist->next = head->prev = NULL;
+ if (sw.ncases > 0)
+ swgen(&sw);
+ branch(lab);
+ head->next->prev = codelist;
+ codelist->next = head->next;
+ codelist = tail;
+}
+static void caselabel(Swtch swp, long val, int lab) {
+ int k;
+
+ if (swp->ncases >= swp->size)
+ {
+ long *vals = swp->values;
+ Symbol *labs = swp->labels;
+ swp->size *= 2;
+ swp->values = newarray(swp->size, sizeof *swp->values, FUNC);
+ swp->labels = newarray(swp->size, sizeof *swp->labels, FUNC);
+ for (k = 0; k < swp->ncases; k++) {
+ swp->values[k] = vals[k];
+ swp->labels[k] = labs[k];
+ }
+ }
+ k = swp->ncases;
+ for ( ; k > 0 && swp->values[k-1] >= val; k--) {
+ swp->values[k] = swp->values[k-1];
+ swp->labels[k] = swp->labels[k-1];
+ }
+ if (k < swp->ncases && swp->values[k] == val)
+ error("duplicate case label `%d'\n", val);
+ swp->values[k] = val;
+ swp->labels[k] = findlabel(lab);
+ ++swp->ncases;
+ if (Aflag >= 2 && swp->ncases == 258)
+ warning("more than 257 cases in a switch\n");
+}
+void swgen(Swtch swp) {
+ int *buckets, k, n;
+ long *v = swp->values;
+
+ buckets = newarray(swp->ncases + 1,
+ sizeof *buckets, FUNC);
+ for (n = k = 0; k < swp->ncases; k++, n++) {
+ buckets[n] = k;
+ while (n > 0 && den(n-1, k) >= density)
+ n--;
+ }
+ buckets[n] = swp->ncases;
+ swcode(swp, buckets, 0, n - 1);
+}
+void swcode(Swtch swp, int b[], int lb, int ub) {
+ int hilab, lolab, l, u, k = (lb + ub)/2;
+ long *v = swp->values;
+
+ if (k > lb && k < ub) {
+ lolab = genlabel(1);
+ hilab = genlabel(1);
+ } else if (k > lb) {
+ lolab = genlabel(1);
+ hilab = swp->deflab->u.l.label;
+ } else if (k < ub) {
+ lolab = swp->deflab->u.l.label;
+ hilab = genlabel(1);
+ } else
+ lolab = hilab = swp->deflab->u.l.label;
+ l = b[k];
+ u = b[k+1] - 1;
+ if (u - l + 1 <= 3)
+ {
+ int i;
+ for (i = l; i <= u; i++)
+ cmp(EQ, swp->sym, v[i], swp->labels[i]->u.l.label);
+ if (k > lb && k < ub)
+ cmp(GT, swp->sym, v[u], hilab);
+ else if (k > lb)
+ cmp(GT, swp->sym, v[u], hilab);
+ else if (k < ub)
+ cmp(LT, swp->sym, v[l], lolab);
+ else
+ assert(lolab == hilab),
+ branch(lolab);
+ walk(NULL, 0, 0);
+ }
+ else {
+ Tree e;
+ Type ty = signedint(swp->sym->type);
+ Symbol table = genident(STATIC,
+ array(voidptype, u - l + 1, 0), GLOBAL);
+ (*IR->defsymbol)(table);
+ if (!isunsigned(swp->sym->type) || v[l] != 0)
+ cmp(LT, swp->sym, v[l], lolab);
+ cmp(GT, swp->sym, v[u], hilab);
+ e = (*optree['-'])(SUB, cast(idtree(swp->sym), ty), cnsttree(ty, v[l]));
+ if (e->type->size < unsignedptr->size)
+ e = cast(e, unsignedlong);
+ walk(tree(JUMP, voidtype,
+ rvalue((*optree['+'])(ADD, pointer(idtree(table)), e)), NULL),
+ 0, 0);
+ code(Switch);
+ codelist->u.swtch.table = table;
+ codelist->u.swtch.sym = swp->sym;
+ codelist->u.swtch.deflab = swp->deflab;
+ codelist->u.swtch.size = u - l + 1;
+ codelist->u.swtch.values = &v[l];
+ codelist->u.swtch.labels = &swp->labels[l];
+ if (v[u] - v[l] + 1 >= 10000)
+ warning("switch generates a huge table\n");
+ }
+ if (k > lb) {
+ assert(lolab != swp->deflab->u.l.label);
+ definelab(lolab);
+ swcode(swp, b, lb, k - 1);
+ }
+ if (k < ub) {
+ assert(hilab != swp->deflab->u.l.label);
+ definelab(hilab);
+ swcode(swp, b, k + 1, ub);
+ }
+}
+static void cmp(int op, Symbol p, long n, int lab) {
+ Type ty = signedint(p->type);
+
+ listnodes(eqtree(op,
+ cast(idtree(p), ty),
+ cnsttree(ty, n)),
+ lab, 0);
+}
+void retcode(Tree p) {
+ Type ty;
+
+ if (p == NULL) {
+ if (events.returns)
+ apply(events.returns, cfunc, NULL);
+ return;
+ }
+ p = pointer(p);
+ ty = assign(freturn(cfunc->type), p);
+ if (ty == NULL) {
+ error("illegal return type; found `%t' expected `%t'\n",
+ p->type, freturn(cfunc->type));
+ return;
+ }
+ p = cast(p, ty);
+ if (retv)
+ {
+ if (iscallb(p))
+ p = tree(RIGHT, p->type,
+ tree(CALL+B, p->type,
+ p->kids[0]->kids[0], idtree(retv)),
+ rvalue(idtree(retv)));
+ else
+ p = asgntree(ASGN, rvalue(idtree(retv)), p);
+ walk(p, 0, 0);
+ if (events.returns)
+ apply(events.returns, cfunc, rvalue(idtree(retv)));
+ return;
+ }
+ if (events.returns)
+ {
+ Symbol t1 = genident(AUTO, p->type, level);
+ addlocal(t1);
+ walk(asgn(t1, p), 0, 0);
+ apply(events.returns, cfunc, idtree(t1));
+ p = idtree(t1);
+ }
+ if (!isfloat(p->type))
+ p = cast(p, promote(p->type));
+ if (isptr(p->type))
+ {
+ Symbol q = localaddr(p);
+ if (q && (q->computed || q->generated))
+ warning("pointer to a %s is an illegal return value\n",
+ q->scope == PARAM ? "parameter" : "local");
+ else if (q)
+ warning("pointer to %s `%s' is an illegal return value\n",
+ q->scope == PARAM ? "parameter" : "local", q->name);
+ }
+ walk(tree(mkop(RET,p->type), p->type, p, NULL), 0, 0);
+}
+void definelab(int lab) {
+ Code cp;
+ Symbol p = findlabel(lab);
+
+ assert(lab);
+ walk(NULL, 0, 0);
+ code(Label)->u.forest = newnode(LABEL+V, NULL, NULL, p);
+ for (cp = codelist->prev; cp->kind <= Label; )
+ cp = cp->prev;
+ while ( cp->kind == Jump
+ && cp->u.forest->kids[0]
+ && specific(cp->u.forest->kids[0]->op) == ADDRG+P
+ && cp->u.forest->kids[0]->syms[0] == p) {
+ assert(cp->u.forest->kids[0]->syms[0]->u.l.label == lab);
+ p->ref--;
+ assert(cp->next);
+ assert(cp->prev);
+ cp->prev->next = cp->next;
+ cp->next->prev = cp->prev;
+ cp = cp->prev;
+ while (cp->kind <= Label)
+ cp = cp->prev;
+ }
+}
+Node jump(int lab) {
+ Symbol p = findlabel(lab);
+
+ p->ref++;
+ return newnode(JUMP+V, newnode(ADDRG+ttob(voidptype), NULL, NULL, p),
+ NULL, NULL);
+}
+void branch(int lab) {
+ Code cp;
+ Symbol p = findlabel(lab);
+
+ assert(lab);
+ walk(NULL, 0, 0);
+ code(Label)->u.forest = jump(lab);
+ for (cp = codelist->prev; cp->kind < Label; )
+ cp = cp->prev;
+ while ( cp->kind == Label
+ && cp->u.forest->op == LABEL+V
+ && !equal(cp->u.forest->syms[0], p)) {
+ equatelab(cp->u.forest->syms[0], p);
+ assert(cp->next);
+ assert(cp->prev);
+ cp->prev->next = cp->next;
+ cp->next->prev = cp->prev;
+ cp = cp->prev;
+ while (cp->kind < Label)
+ cp = cp->prev;
+ }
+ if (cp->kind == Jump || cp->kind == Switch) {
+ p->ref--;
+ codelist->prev->next = NULL;
+ codelist = codelist->prev;
+ } else {
+ codelist->kind = Jump;
+ if (cp->kind == Label
+ && cp->u.forest->op == LABEL+V
+ && equal(cp->u.forest->syms[0], p))
+ warning("source code specifies an infinite loop");
+ }
+}
+void equatelab(Symbol old, Symbol new) {
+ assert(old->u.l.equatedto == NULL);
+ old->u.l.equatedto = new;
+ new->ref++;
+}
+static int equal(Symbol lprime, Symbol dst) {
+ assert(dst && lprime);
+ for ( ; dst; dst = dst->u.l.equatedto)
+ if (lprime == dst)
+ return 1;
+ return 0;
+}
+/* dostmt - do statement while ( expression ) */
+static void dostmt(int lab, Swtch swp, int lev) {
+ refinc *= 10.0;
+ t = gettok();
+ definelab(lab);
+ statement(lab, swp, lev);
+ definelab(lab + 1);
+ expect(WHILE);
+ expect('(');
+ definept(NULL);
+ walk(conditional(')'), lab, 0);
+ if (findlabel(lab + 2)->ref)
+ definelab(lab + 2);
+}
+
+/* foldcond - check if initial test in for(e1;e2;e3) S is necessary */
+static int foldcond(Tree e1, Tree e2) {
+ int op = generic(e2->op);
+ Symbol v;
+
+ if (e1 == 0 || e2 == 0)
+ return 0;
+ if (generic(e1->op) == ASGN && isaddrop(e1->kids[0]->op)
+ && generic(e1->kids[1]->op) == CNST) {
+ v = e1->kids[0]->u.sym;
+ e1 = e1->kids[1];
+ } else
+ return 0;
+ if ((op==LE || op==LT || op==EQ || op==NE || op==GT || op==GE)
+ && generic(e2->kids[0]->op) == INDIR
+ && e2->kids[0]->kids[0]->u.sym == v
+ && e2->kids[1]->op == e1->op) {
+ e1 = simplify(op, e2->type, e1, e2->kids[1]);
+ if (e1->op == CNST+I)
+ return e1->u.v.i;
+ }
+ return 0;
+}
+
+/* localaddr - returns q if p yields the address of local/parameter q; otherwise returns 0 */
+static Symbol localaddr(Tree p) {
+ if (p == NULL)
+ return NULL;
+ switch (generic(p->op)) {
+ case INDIR: case CALL: case ARG:
+ return NULL;
+ case ADDRL: case ADDRF:
+ return p->u.sym;
+ case RIGHT: case ASGN:
+ if (p->kids[1])
+ return localaddr(p->kids[1]);
+ return localaddr(p->kids[0]);
+ case COND: {
+ Symbol q;
+ assert(p->kids[1] && p->kids[1]->op == RIGHT);
+ if ((q = localaddr(p->kids[1]->kids[0])) != NULL)
+ return q;
+ return localaddr(p->kids[1]->kids[1]);
+ }
+ default: {
+ Symbol q;
+ if (p->kids[0] && (q = localaddr(p->kids[0])) != NULL)
+ return q;
+ return localaddr(p->kids[1]);
+ }
+ }
+}
+
+/* whilestmt - while ( expression ) statement */
+static void whilestmt(int lab, Swtch swp, int lev) {
+ Coordinate pt;
+ Tree e;
+
+ refinc *= 10.0;
+ t = gettok();
+ expect('(');
+ walk(NULL, 0, 0);
+ pt = src;
+ e = texpr(conditional, ')', FUNC);
+ branch(lab + 1);
+ definelab(lab);
+ statement(lab, swp, lev);
+ definelab(lab + 1);
+ definept(&pt);
+ walk(e, lab, 0);
+ if (findlabel(lab + 2)->ref)
+ definelab(lab + 2);
+}
diff --git a/src/tools/lcc/src/string.c b/src/tools/lcc/src/string.c
new file mode 100644
index 0000000..73cfc85
--- /dev/null
+++ b/src/tools/lcc/src/string.c
@@ -0,0 +1,122 @@
+#include "c.h"
+
+
+static struct string {
+ char *str;
+ int len;
+ struct string *link;
+} *buckets[1024];
+static int scatter[] = { /* map characters to random values */
+ 2078917053, 143302914, 1027100827, 1953210302, 755253631,
+ 2002600785, 1405390230, 45248011, 1099951567, 433832350,
+ 2018585307, 438263339, 813528929, 1703199216, 618906479,
+ 573714703, 766270699, 275680090, 1510320440, 1583583926,
+ 1723401032, 1965443329, 1098183682, 1636505764, 980071615,
+ 1011597961, 643279273, 1315461275, 157584038, 1069844923,
+ 471560540, 89017443, 1213147837, 1498661368, 2042227746,
+ 1968401469, 1353778505, 1300134328, 2013649480, 306246424,
+ 1733966678, 1884751139, 744509763, 400011959, 1440466707,
+ 1363416242, 973726663, 59253759, 1639096332, 336563455,
+ 1642837685, 1215013716, 154523136, 593537720, 704035832,
+ 1134594751, 1605135681, 1347315106, 302572379, 1762719719,
+ 269676381, 774132919, 1851737163, 1482824219, 125310639,
+ 1746481261, 1303742040, 1479089144, 899131941, 1169907872,
+ 1785335569, 485614972, 907175364, 382361684, 885626931,
+ 200158423, 1745777927, 1859353594, 259412182, 1237390611,
+ 48433401, 1902249868, 304920680, 202956538, 348303940,
+ 1008956512, 1337551289, 1953439621, 208787970, 1640123668,
+ 1568675693, 478464352, 266772940, 1272929208, 1961288571,
+ 392083579, 871926821, 1117546963, 1871172724, 1771058762,
+ 139971187, 1509024645, 109190086, 1047146551, 1891386329,
+ 994817018, 1247304975, 1489680608, 706686964, 1506717157,
+ 579587572, 755120366, 1261483377, 884508252, 958076904,
+ 1609787317, 1893464764, 148144545, 1415743291, 2102252735,
+ 1788268214, 836935336, 433233439, 2055041154, 2109864544,
+ 247038362, 299641085, 834307717, 1364585325, 23330161,
+ 457882831, 1504556512, 1532354806, 567072918, 404219416,
+ 1276257488, 1561889936, 1651524391, 618454448, 121093252,
+ 1010757900, 1198042020, 876213618, 124757630, 2082550272,
+ 1834290522, 1734544947, 1828531389, 1982435068, 1002804590,
+ 1783300476, 1623219634, 1839739926, 69050267, 1530777140,
+ 1802120822, 316088629, 1830418225, 488944891, 1680673954,
+ 1853748387, 946827723, 1037746818, 1238619545, 1513900641,
+ 1441966234, 367393385, 928306929, 946006977, 985847834,
+ 1049400181, 1956764878, 36406206, 1925613800, 2081522508,
+ 2118956479, 1612420674, 1668583807, 1800004220, 1447372094,
+ 523904750, 1435821048, 923108080, 216161028, 1504871315,
+ 306401572, 2018281851, 1820959944, 2136819798, 359743094,
+ 1354150250, 1843084537, 1306570817, 244413420, 934220434,
+ 672987810, 1686379655, 1301613820, 1601294739, 484902984,
+ 139978006, 503211273, 294184214, 176384212, 281341425,
+ 228223074, 147857043, 1893762099, 1896806882, 1947861263,
+ 1193650546, 273227984, 1236198663, 2116758626, 489389012,
+ 593586330, 275676551, 360187215, 267062626, 265012701,
+ 719930310, 1621212876, 2108097238, 2026501127, 1865626297,
+ 894834024, 552005290, 1404522304, 48964196, 5816381,
+ 1889425288, 188942202, 509027654, 36125855, 365326415,
+ 790369079, 264348929, 513183458, 536647531, 13672163,
+ 313561074, 1730298077, 286900147, 1549759737, 1699573055,
+ 776289160, 2143346068, 1975249606, 1136476375, 262925046,
+ 92778659, 1856406685, 1884137923, 53392249, 1735424165,
+ 1602280572
+};
+char *string(const char *str) {
+ const char *s;
+
+ for (s = str; *s; s++)
+ ;
+ return stringn(str, s - str);
+}
+char *stringd(long n) {
+ char str[25], *s = str + sizeof (str);
+ unsigned long m;
+
+ if (n == LONG_MIN)
+ m = (unsigned long)LONG_MAX + 1;
+ else if (n < 0)
+ m = -n;
+ else
+ m = n;
+ do
+ *--s = m%10 + '0';
+ while ((m /= 10) != 0);
+ if (n < 0)
+ *--s = '-';
+ return stringn(s, str + sizeof (str) - s);
+}
+char *stringn(const char *str, int len) {
+ int i;
+ unsigned int h;
+ const char *end;
+ struct string *p;
+
+ assert(str);
+ for (h = 0, i = len, end = str; i > 0; i--)
+ h = (h<<1) + scatter[*(unsigned char *)end++];
+ h &= NELEMS(buckets)-1;
+ for (p = buckets[h]; p; p = p->link)
+ if (len == p->len) {
+ const char *s1 = str;
+ char *s2 = p->str;
+ do {
+ if (s1 == end)
+ return p->str;
+ } while (*s1++ == *s2++);
+ }
+ {
+ static char *next, *strlimit;
+ if (len + 1 >= strlimit - next) {
+ int n = len + 4*1024;
+ next = allocate(n, PERM);
+ strlimit = next + n;
+ }
+ NEW(p, PERM);
+ p->len = len;
+ for (p->str = next; str < end; )
+ *next++ = *str++;
+ *next++ = 0;
+ p->link = buckets[h];
+ buckets[h] = p;
+ return p->str;
+ }
+}
diff --git a/src/tools/lcc/src/sym.c b/src/tools/lcc/src/sym.c
new file mode 100644
index 0000000..2a1cfeb
--- /dev/null
+++ b/src/tools/lcc/src/sym.c
@@ -0,0 +1,314 @@
+#include "c.h"
+#include <stdio.h>
+
+
+#define equalp(x) v.x == p->sym.u.c.v.x
+
+struct table {
+ int level;
+ Table previous;
+ struct entry {
+ struct symbol sym;
+ struct entry *link;
+ } *buckets[256];
+ Symbol all;
+};
+#define HASHSIZE NELEMS(((Table)0)->buckets)
+static struct table
+ cns = { CONSTANTS },
+ ext = { GLOBAL },
+ ids = { GLOBAL },
+ tys = { GLOBAL };
+Table constants = &cns;
+Table externals = &ext;
+Table identifiers = &ids;
+Table globals = &ids;
+Table types = &tys;
+Table labels;
+int level = GLOBAL;
+static int tempid;
+List loci, symbols;
+
+Table table(Table tp, int level) {
+ Table new;
+
+ NEW0(new, FUNC);
+ new->previous = tp;
+ new->level = level;
+ if (tp)
+ new->all = tp->all;
+ return new;
+}
+void foreach(Table tp, int lev, void (*apply)(Symbol, void *), void *cl) {
+ assert(tp);
+ while (tp && tp->level > lev)
+ tp = tp->previous;
+ if (tp && tp->level == lev) {
+ Symbol p;
+ Coordinate sav;
+ sav = src;
+ for (p = tp->all; p && p->scope == lev; p = p->up) {
+ src = p->src;
+ (*apply)(p, cl);
+ }
+ src = sav;
+ }
+}
+void enterscope(void) {
+ if (++level == LOCAL)
+ tempid = 0;
+}
+void exitscope(void) {
+ rmtypes(level);
+ if (types->level == level)
+ types = types->previous;
+ if (identifiers->level == level) {
+ if (Aflag >= 2) {
+ int n = 0;
+ Symbol p;
+ for (p = identifiers->all; p && p->scope == level; p = p->up)
+ if (++n > 127) {
+ warning("more than 127 identifiers declared in a block\n");
+ break;
+ }
+ }
+ identifiers = identifiers->previous;
+ }
+ assert(level >= GLOBAL);
+ --level;
+}
+Symbol install(const char *name, Table *tpp, int level, int arena) {
+ Table tp = *tpp;
+ struct entry *p;
+ unsigned h = (unsigned long)name&(HASHSIZE-1);
+
+ assert(level == 0 || level >= tp->level);
+ if (level > 0 && tp->level < level)
+ tp = *tpp = table(tp, level);
+ NEW0(p, arena);
+ p->sym.name = (char *)name;
+ p->sym.scope = level;
+ p->sym.up = tp->all;
+ tp->all = &p->sym;
+ p->link = tp->buckets[h];
+ tp->buckets[h] = p;
+ return &p->sym;
+}
+Symbol relocate(const char *name, Table src, Table dst) {
+ struct entry *p, **q;
+ Symbol *r;
+ unsigned h = (unsigned long)name&(HASHSIZE-1);
+
+ for (q = &src->buckets[h]; *q; q = &(*q)->link)
+ if (name == (*q)->sym.name)
+ break;
+ assert(*q);
+ /*
+ Remove the entry from src's hash chain
+ and from its list of all symbols.
+ */
+ p = *q;
+ *q = (*q)->link;
+ for (r = &src->all; *r && *r != &p->sym; r = &(*r)->up)
+ ;
+ assert(*r == &p->sym);
+ *r = p->sym.up;
+ /*
+ Insert the entry into dst's hash chain
+ and into its list of all symbols.
+ Return the symbol-table entry.
+ */
+ p->link = dst->buckets[h];
+ dst->buckets[h] = p;
+ p->sym.up = dst->all;
+ dst->all = &p->sym;
+ return &p->sym;
+}
+Symbol lookup(const char *name, Table tp) {
+ struct entry *p;
+ unsigned h = (unsigned long)name&(HASHSIZE-1);
+
+ assert(tp);
+ do
+ for (p = tp->buckets[h]; p; p = p->link)
+ if (name == p->sym.name)
+ return &p->sym;
+ while ((tp = tp->previous) != NULL);
+ return NULL;
+}
+int genlabel(int n) {
+ static int label = 1;
+
+ label += n;
+ return label - n;
+}
+Symbol findlabel(int lab) {
+ struct entry *p;
+ unsigned h = lab&(HASHSIZE-1);
+
+ for (p = labels->buckets[h]; p; p = p->link)
+ if (lab == p->sym.u.l.label)
+ return &p->sym;
+ NEW0(p, FUNC);
+ p->sym.name = stringd(lab);
+ p->sym.scope = LABELS;
+ p->sym.up = labels->all;
+ labels->all = &p->sym;
+ p->link = labels->buckets[h];
+ labels->buckets[h] = p;
+ p->sym.generated = 1;
+ p->sym.u.l.label = lab;
+ (*IR->defsymbol)(&p->sym);
+ return &p->sym;
+}
+Symbol constant(Type ty, Value v) {
+ struct entry *p;
+ unsigned h = v.u&(HASHSIZE-1);
+
+ ty = unqual(ty);
+ for (p = constants->buckets[h]; p; p = p->link)
+ if (eqtype(ty, p->sym.type, 1))
+ switch (ty->op) {
+ case INT: if (equalp(i)) return &p->sym; break;
+ case UNSIGNED: if (equalp(u)) return &p->sym; break;
+ case FLOAT: if (equalp(d)) return &p->sym; break;
+ case FUNCTION: if (equalp(g)) return &p->sym; break;
+ case ARRAY:
+ case POINTER: if (equalp(p)) return &p->sym; break;
+ default: assert(0);
+ }
+ NEW0(p, PERM);
+ p->sym.name = vtoa(ty, v);
+ p->sym.scope = CONSTANTS;
+ p->sym.type = ty;
+ p->sym.sclass = STATIC;
+ p->sym.u.c.v = v;
+ p->link = constants->buckets[h];
+ p->sym.up = constants->all;
+ constants->all = &p->sym;
+ constants->buckets[h] = p;
+ if (ty->u.sym && !ty->u.sym->addressed)
+ (*IR->defsymbol)(&p->sym);
+ p->sym.defined = 1;
+ return &p->sym;
+}
+Symbol intconst(int n) {
+ Value v;
+
+ v.i = n;
+ return constant(inttype, v);
+}
+Symbol genident(int scls, Type ty, int lev) {
+ Symbol p;
+
+ NEW0(p, lev >= LOCAL ? FUNC : PERM);
+ p->name = stringd(genlabel(1));
+ p->scope = lev;
+ p->sclass = scls;
+ p->type = ty;
+ p->generated = 1;
+ if (lev == GLOBAL)
+ (*IR->defsymbol)(p);
+ return p;
+}
+
+Symbol temporary(int scls, Type ty) {
+ Symbol p;
+
+ NEW0(p, FUNC);
+ p->name = stringd(++tempid);
+ p->scope = level < LOCAL ? LOCAL : level;
+ p->sclass = scls;
+ p->type = ty;
+ p->temporary = 1;
+ p->generated = 1;
+ return p;
+}
+Symbol newtemp(int sclass, int tc, int size) {
+ Symbol p = temporary(sclass, btot(tc, size));
+
+ (*IR->local)(p);
+ p->defined = 1;
+ return p;
+}
+
+Symbol allsymbols(Table tp) {
+ return tp->all;
+}
+
+void locus(Table tp, Coordinate *cp) {
+ loci = append(cp, loci);
+ symbols = append(allsymbols(tp), symbols);
+}
+
+void use(Symbol p, Coordinate src) {
+ Coordinate *cp;
+
+ NEW(cp, PERM);
+ *cp = src;
+ p->uses = append(cp, p->uses);
+}
+/* findtype - find type ty in identifiers */
+Symbol findtype(Type ty) {
+ Table tp = identifiers;
+ int i;
+ struct entry *p;
+
+ assert(tp);
+ do
+ for (i = 0; i < HASHSIZE; i++)
+ for (p = tp->buckets[i]; p; p = p->link)
+ if (p->sym.type == ty && p->sym.sclass == TYPEDEF)
+ return &p->sym;
+ while ((tp = tp->previous) != NULL);
+ return NULL;
+}
+
+/* mkstr - make a string constant */
+Symbol mkstr(char *str) {
+ Value v;
+ Symbol p;
+
+ v.p = str;
+ p = constant(array(chartype, strlen(v.p) + 1, 0), v);
+ if (p->u.c.loc == NULL)
+ p->u.c.loc = genident(STATIC, p->type, GLOBAL);
+ return p;
+}
+
+/* mksymbol - make a symbol for name, install in &globals if sclass==EXTERN */
+Symbol mksymbol(int sclass, const char *name, Type ty) {
+ Symbol p;
+
+ if (sclass == EXTERN)
+ p = install(string(name), &globals, GLOBAL, PERM);
+ else {
+ NEW0(p, PERM);
+ p->name = string(name);
+ p->scope = GLOBAL;
+ }
+ p->sclass = sclass;
+ p->type = ty;
+ (*IR->defsymbol)(p);
+ p->defined = 1;
+ return p;
+}
+
+/* vtoa - return string for the constant v of type ty */
+char *vtoa(Type ty, Value v) {
+
+ ty = unqual(ty);
+ switch (ty->op) {
+ case INT: return stringd(v.i);
+ case UNSIGNED: return stringf((v.u&~0x7FFF) ? "0x%X" : "%U", v.u);
+ case FLOAT: return stringf("%g", (double)v.d);
+ case ARRAY:
+ if (ty->type == chartype || ty->type == signedchar
+ || ty->type == unsignedchar)
+ return v.p;
+ return stringf("%p", v.p);
+ case POINTER: return stringf("%p", v.p);
+ case FUNCTION: return stringf("%p", v.g);
+ }
+ assert(0); return NULL;
+}
diff --git a/src/tools/lcc/src/symbolic.c b/src/tools/lcc/src/symbolic.c
new file mode 100644
index 0000000..affa67a
--- /dev/null
+++ b/src/tools/lcc/src/symbolic.c
@@ -0,0 +1,494 @@
+#include <time.h>
+#include <ctype.h>
+#include "c.h"
+
+#define I(f) s_##f
+
+static Node *tail;
+static int off, maxoff, uid = 0, verbose = 0, html = 0;
+
+static const char *yyBEGIN(const char *tag) {
+ if (html)
+ print("<%s>", tag);
+ return tag;
+}
+
+static void yyEND(const char *tag) {
+ if (html)
+ print("</%s>", tag);
+ if (isupper(*tag))
+ print("\n");
+}
+
+#define BEGIN(tag) do { const char *yytag=yyBEGIN(#tag);
+#define END yyEND(yytag); } while (0)
+#define ITEM BEGIN(li)
+#define START BEGIN(LI)
+#define ANCHOR(attr,code) do { const char *yytag="a"; if (html) { printf("<a " #attr "=\""); code; print("\">"); }
+#define NEWLINE print(html ? "<br>\n" : "\n")
+
+static void emitCoord(Coordinate src) {
+ if (src.file && *src.file) {
+ ANCHOR(href,print("%s", src.file)); print("%s", src.file); END;
+ print(":");
+ }
+ print("%d.%d", src.y, src.x);
+}
+
+static void emitString(int len, const char *s) {
+ for ( ; len-- > 0; s++)
+ if (*s == '&' && html)
+ print("&amp;");
+ else if (*s == '<' && html)
+ print("&lt;");
+ else if (*s == '>' && html)
+ print("&lt;");
+ else if (*s == '"' || *s == '\\')
+ print("\\%c", *s);
+ else if (*s >= ' ' && *s < 0177)
+ print("%c", *s);
+ else
+ print("\\%d%d%d", (*s>>6)&3, (*s>>3)&7, *s&7);
+}
+
+static void emitSymRef(Symbol p) {
+ (*IR->defsymbol)(p);
+ ANCHOR(href,print("#%s", p->x.name)); BEGIN(code); print("%s", p->name); END; END;
+}
+
+static void emitSymbol(Symbol p) {
+ (*IR->defsymbol)(p);
+ ANCHOR(name,print("%s", p->x.name)); BEGIN(code); print("%s", p->name); END; END;
+ BEGIN(ul);
+#define xx(field,code) ITEM; if (!html) print(" "); print(#field "="); code; END
+ if (verbose && (src.y || src.x))
+ xx(src,emitCoord(p->src));
+ xx(type,print("%t", p->type));
+ xx(sclass,print("%k", p->sclass));
+ switch (p->scope) {
+ case CONSTANTS: xx(scope,print("CONSTANTS")); break;
+ case LABELS: xx(scope,print("LABELS")); break;
+ case GLOBAL: xx(scope,print("GLOBAL")); break;
+ case PARAM: xx(scope,print("PARAM")); break;
+ case LOCAL: xx(scope,print("LOCAL")); break;
+ default:
+ if (p->scope > LOCAL)
+ xx(scope,print("LOCAL+%d", p->scope-LOCAL));
+ else
+ xx(scope,print("%d", p->scope));
+ }
+ if (p->scope >= PARAM && p->sclass != STATIC)
+ xx(offset,print("%d", p->x.offset));
+ xx(ref,print("%f", p->ref));
+ if (p->temporary && p->u.t.cse)
+ xx(u.t.cse,print("%p", p->u.t.cse));
+ END;
+#undef xx
+}
+
+/* address - initialize q for addressing expression p+n */
+static void I(address)(Symbol q, Symbol p, long n) {
+ q->name = stringf("%s%s%D", p->name, n > 0 ? "+" : "", n);
+ (*IR->defsymbol)(q);
+ START; print("address "); emitSymbol(q); END;
+}
+
+/* blockbeg - start a block */
+static void I(blockbeg)(Env *e) {
+ e->offset = off;
+ START; print("blockbeg off=%d", off); END;
+}
+
+/* blockend - start a block */
+static void I(blockend)(Env *e) {
+ if (off > maxoff)
+ maxoff = off;
+ START; print("blockend off=%d", off); END;
+ off = e->offset;
+}
+
+/* defaddress - initialize an address */
+static void I(defaddress)(Symbol p){
+ START; print("defaddress "); emitSymRef(p); END;
+}
+
+/* defconst - define a constant */
+static void I(defconst)(int suffix, int size, Value v) {
+ START;
+ print("defconst ");
+ switch (suffix) {
+ case I:
+ print("int.%d ", size);
+ BEGIN(code);
+ if (size > sizeof (int))
+ print("%D", v.i);
+ else
+ print("%d", (int)v.i);
+ END;
+ break;
+ case U:
+ print("unsigned.%d ", size);
+ BEGIN(code);
+ if (size > sizeof (unsigned))
+ print("%U", v.u);
+ else
+ print("%u", (unsigned)v.u);
+ END;
+ break;
+ case P: print("void*.%d ", size); BEGIN(code); print("%p", v.p); END; break;
+ case F: print("float.%d ", size); BEGIN(code); print("%g", (double)v.d); END; break;
+ default: assert(0);
+ }
+ END;
+}
+
+/* defstring - emit a string constant */
+static void I(defstring)(int len, char *s) {
+ START; print("defstring ");
+ BEGIN(code); print("\""); emitString(len, s); print("\""); END;
+ END;
+}
+
+/* defsymbol - define a symbol: initialize p->x */
+static void I(defsymbol)(Symbol p) {
+ if (p->x.name == NULL)
+ p->x.name = stringd(++uid);
+}
+
+/* emit - emit the dags on list p */
+static void I(emit)(Node p){
+ ITEM;
+ if (!html)
+ print(" ");
+ for (; p; p = p->x.next) {
+ if (p->op == LABEL+V) {
+ assert(p->syms[0]);
+ ANCHOR(name,print("%s", p->syms[0]->x.name));
+ BEGIN(code); print("%s", p->syms[0]->name); END;
+ END;
+ print(":");
+ } else {
+ int i;
+ if (p->x.listed) {
+ BEGIN(strong); print("%d", p->x.inst); END; print("'");
+ print(" %s", opname(p->op));
+ } else
+ print("%d. %s", p->x.inst, opname(p->op));
+ if (p->count > 1)
+ print(" count=%d", p->count);
+ for (i = 0; i < NELEMS(p->kids) && p->kids[i]; i++)
+ print(" #%d", p->kids[i]->x.inst);
+ if (generic(p->op) == CALL && p->syms[0] && p->syms[0]->type)
+ print(" {%t}", p->syms[0]->type);
+ else
+ for (i = 0; i < NELEMS(p->syms) && p->syms[i]; i++) {
+ print(" ");
+ if (p->syms[i]->scope == CONSTANTS)
+ print(p->syms[i]->name);
+ else
+ emitSymRef(p->syms[i]);
+ }
+ }
+ NEWLINE;
+ }
+ END;
+}
+
+/* export - announce p as exported */
+static void I(export)(Symbol p) {
+ START; print("export "); emitSymRef(p); END;
+}
+
+/* function - generate code for a function */
+static void I(function)(Symbol f, Symbol caller[], Symbol callee[], int ncalls) {
+ int i;
+
+ (*IR->defsymbol)(f);
+ off = 0;
+ for (i = 0; caller[i] && callee[i]; i++) {
+ off = roundup(off, caller[i]->type->align);
+ caller[i]->x.offset = callee[i]->x.offset = off;
+ off += caller[i]->type->size;
+ }
+ if (!html) {
+ print("function ");
+ emitSymbol(f);
+ print(" ncalls=%d\n", ncalls);
+ for (i = 0; caller[i]; i++)
+ START; print("caller "); emitSymbol(caller[i]); END;
+ for (i = 0; callee[i]; i++)
+ START; print("callee "); emitSymbol(callee[i]); END;
+ } else {
+ START;
+ print("function");
+ BEGIN(UL);
+#define xx(field,code) ITEM; print(#field "="); code; END
+ xx(f,emitSymbol(f));
+ xx(ncalls,print("%d", ncalls));
+ if (caller[0]) {
+ ITEM; print("caller"); BEGIN(OL);
+ for (i = 0; caller[i]; i++)
+ ITEM; emitSymbol(caller[i]); END;
+ END; END;
+ ITEM; print("callee"); BEGIN(OL);
+ for (i = 0; callee[i]; i++)
+ ITEM; emitSymbol(callee[i]); END;
+ END; END;
+ } else {
+ xx(caller,BEGIN(em); print("empty"); END);
+ xx(callee,BEGIN(em); print("empty"); END);
+ }
+ END;
+ END;
+ }
+ maxoff = off = 0;
+ gencode(caller, callee);
+ if (html)
+ START; print("emitcode"); BEGIN(ul); emitcode(); END; END;
+ else
+ emitcode();
+ START; print("maxoff=%d", maxoff); END;
+#undef xx
+}
+
+/* visit - generate code for *p */
+static int visit(Node p, int n) {
+ if (p && p->x.inst == 0) {
+ p->x.inst = ++n;
+ n = visit(p->kids[0], n);
+ n = visit(p->kids[1], n);
+ *tail = p;
+ tail = &p->x.next;
+ }
+ return n;
+}
+
+/* gen0 - generate code for the dags on list p */
+static Node I(gen)(Node p) {
+ int n;
+ Node nodelist;
+
+ tail = &nodelist;
+ for (n = 0; p; p = p->link) {
+ switch (generic(p->op)) { /* check for valid forest */
+ case CALL:
+ assert(IR->wants_dag || p->count == 0);
+ break;
+ case ARG:
+ case ASGN: case JUMP: case LABEL: case RET:
+ case EQ: case GE: case GT: case LE: case LT: case NE:
+ assert(p->count == 0);
+ break;
+ case INDIR:
+ assert(IR->wants_dag && p->count > 0);
+ break;
+ default:
+ assert(0);
+ }
+ check(p);
+ p->x.listed = 1;
+ n = visit(p, n);
+ }
+ *tail = 0;
+ return nodelist;
+}
+
+/* global - announce a global */
+static void I(global)(Symbol p) {
+ START; print("global "); emitSymbol(p); END;
+}
+
+/* import - import a symbol */
+static void I(import)(Symbol p) {
+ START; print("import "); emitSymRef(p); END;
+}
+
+/* local - local variable */
+static void I(local)(Symbol p) {
+ if (p->temporary)
+ p->name = stringf("t%s", p->name);
+ (*IR->defsymbol)(p);
+ off = roundup(off, p->type->align);
+ p->x.offset = off;
+ off += p->type->size;
+ START; print(p->temporary ? "temporary " : "local "); emitSymbol(p); END;
+}
+
+/* progbeg - beginning of program */
+static void I(progbeg)(int argc, char *argv[]) {
+ int i;
+
+ for (i = 1; i < argc; i++)
+ if (strcmp(argv[i], "-v") == 0)
+ verbose++;
+ else if (strcmp(argv[i], "-html") == 0)
+ html++;
+ if (html) {
+ print("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n");
+ print("<html>");
+ BEGIN(head);
+ if (firstfile && *firstfile)
+ BEGIN(title); emitString(strlen(firstfile), firstfile); END;
+ print("<link rev=made href=\"mailto:drh@microsoft.com\">\n");
+ END;
+ print("<body>\n");
+ if (firstfile && *firstfile)
+ BEGIN(h1); emitString(strlen(firstfile), firstfile); END;
+ BEGIN(P); BEGIN(em);
+ print("Links lead from uses of identifiers and labels to their definitions.");
+ END; END;
+ print("<ul>\n");
+ START;
+ print("progbeg");
+ BEGIN(ol);
+ for (i = 1; i < argc; i++) {
+ ITEM;
+ BEGIN(code); print("\""); emitString(strlen(argv[i]), argv[i]); print("\""); END;
+ END;
+ }
+ END;
+ END;
+ }
+}
+
+/* progend - end of program */
+static void I(progend)(void) {
+ START; print("progend"); END;
+ if (html) {
+ time_t t;
+ print("</ul>\n");
+ time(&t);
+ print("<hr><address>%s</address>\n", ctime(&t));
+ print("</body></html>\n");
+ }
+}
+
+/* segment - switch to segment s */
+static void I(segment)(int s) {
+ START; print("segment %s", &"text\0bss\0.data\0lit\0.sym\0."[5*s-5]); END;
+}
+
+/* space - initialize n bytes of space */
+static void I(space)(int n) {
+ START; print("space %d", n); END;
+}
+
+static void I(stabblock)(int brace, int lev, Symbol *p) {}
+
+/* stabend - finalize stab output */
+static void I(stabend)(Coordinate *cp, Symbol p, Coordinate **cpp, Symbol *sp, Symbol *stab) {
+ int i;
+
+ if (p)
+ emitSymRef(p);
+ print("\n");
+ if (cpp && sp)
+ for (i = 0; cpp[i] && sp[i]; i++) {
+ print("%w.%d: ", cpp[i], cpp[i]->x);
+ emitSymRef(sp[i]);
+ print("\n");
+ }
+}
+
+static void I(stabfend)(Symbol p, int lineno) {}
+static void I(stabinit)(char *file, int argc, char *argv[]) {}
+
+/* stabline - emit line number information for source coordinate *cp */
+static void I(stabline)(Coordinate *cp) {
+ if (cp->file)
+ print("%s:", cp->file);
+ print("%d.%d:\n", cp->y, cp->x);
+}
+
+static void I(stabsym)(Symbol p) {}
+static void I(stabtype)(Symbol p) {}
+
+Interface symbolicIR = {
+ {1, 1, 0}, /* char */
+ {2, 2, 0}, /* short */
+ {4, 4, 0}, /* int */
+ {4, 4, 0}, /* long */
+ {4, 4, 0}, /* long long */
+ {4, 4, 1}, /* float */
+ {8, 8, 1}, /* double */
+ {8, 8, 1}, /* long double */
+ {4, 4, 0}, /* T* */
+ {0, 4, 0}, /* struct */
+ 0, /* little_endian */
+ 0, /* mulops_calls */
+ 0, /* wants_callb */
+ 1, /* wants_argb */
+ 1, /* left_to_right */
+ 1, /* wants_dag */
+ 0, /* unsigned_char */
+ I(address),
+ I(blockbeg),
+ I(blockend),
+ I(defaddress),
+ I(defconst),
+ I(defstring),
+ I(defsymbol),
+ I(emit),
+ I(export),
+ I(function),
+ I(gen),
+ I(global),
+ I(import),
+ I(local),
+ I(progbeg),
+ I(progend),
+ I(segment),
+ I(space),
+ I(stabblock),
+ I(stabend),
+ I(stabfend),
+ I(stabinit),
+ I(stabline),
+ I(stabsym),
+ I(stabtype)
+};
+
+Interface symbolic64IR = {
+ {1, 1, 0}, /* char */
+ {2, 2, 0}, /* short */
+ {4, 4, 0}, /* int */
+ {8, 8, 0}, /* long */
+ {8, 8, 0}, /* long long */
+ {4, 4, 1}, /* float */
+ {8, 8, 1}, /* double */
+ {8, 8, 1}, /* long double */
+ {8, 8, 0}, /* T* */
+ {0, 1, 0}, /* struct */
+ 1, /* little_endian */
+ 0, /* mulops_calls */
+ 0, /* wants_callb */
+ 1, /* wants_argb */
+ 1, /* left_to_right */
+ 1, /* wants_dag */
+ 0, /* unsigned_char */
+ I(address),
+ I(blockbeg),
+ I(blockend),
+ I(defaddress),
+ I(defconst),
+ I(defstring),
+ I(defsymbol),
+ I(emit),
+ I(export),
+ I(function),
+ I(gen),
+ I(global),
+ I(import),
+ I(local),
+ I(progbeg),
+ I(progend),
+ I(segment),
+ I(space),
+ I(stabblock),
+ I(stabend),
+ I(stabfend),
+ I(stabinit),
+ I(stabline),
+ I(stabsym),
+ I(stabtype)
+};
diff --git a/src/tools/lcc/src/token.h b/src/tools/lcc/src/token.h
new file mode 100644
index 0000000..d309f9b
--- /dev/null
+++ b/src/tools/lcc/src/token.h
@@ -0,0 +1,133 @@
+/*
+xx(symbol, value, prec, op, optree, kind, string)
+*/
+yy(0, 0, 0, 0, 0, 0, 0)
+xx(FLOAT, 1, 0, 0, 0, CHAR, "float")
+xx(DOUBLE, 2, 0, 0, 0, CHAR, "double")
+xx(CHAR, 3, 0, 0, 0, CHAR, "char")
+xx(SHORT, 4, 0, 0, 0, CHAR, "short")
+xx(INT, 5, 0, 0, 0, CHAR, "int")
+xx(UNSIGNED, 6, 0, 0, 0, CHAR, "unsigned")
+xx(POINTER, 7, 0, 0, 0, 0, "pointer")
+xx(VOID, 8, 0, 0, 0, CHAR, "void")
+xx(STRUCT, 9, 0, 0, 0, CHAR, "struct")
+xx(UNION, 10, 0, 0, 0, CHAR, "union")
+xx(FUNCTION, 11, 0, 0, 0, 0, "function")
+xx(ARRAY, 12, 0, 0, 0, 0, "array")
+xx(ENUM, 13, 0, 0, 0, CHAR, "enum")
+xx(LONG, 14, 0, 0, 0, CHAR, "long")
+xx(CONST, 15, 0, 0, 0, CHAR, "const")
+xx(VOLATILE, 16, 0, 0, 0, CHAR, "volatile")
+yy(0, 17, 0, 0, 0, 0, 0)
+yy(0, 18, 0, 0, 0, 0, 0)
+yy(0, 19, 0, 0, 0, 0, 0)
+yy(0, 20, 0, 0, 0, 0, 0)
+yy(0, 21, 0, 0, 0, 0, 0)
+yy(0, 22, 0, 0, 0, 0, 0)
+yy(0, 23, 0, 0, 0, 0, 0)
+yy(0, 24, 0, 0, 0, 0, 0)
+yy(0, 25, 0, 0, 0, 0, 0)
+yy(0, 26, 0, 0, 0, 0, 0)
+yy(0, 27, 0, 0, 0, 0, 0)
+yy(0, 28, 0, 0, 0, 0, "long long")
+yy(0, 29, 0, 0, 0, 0, 0)
+yy(0, 30, 0, 0, 0, 0, 0)
+yy(0, 31, 0, 0, 0, 0, "const volatile")
+xx(ID, 32, 0, 0, 0, ID, "identifier")
+yy(0, 33, 0, 0, 0, ID, "!")
+xx(FCON, 34, 0, 0, 0, ID, "floating constant")
+xx(ICON, 35, 0, 0, 0, ID, "integer constant")
+xx(SCON, 36, 0, 0, 0, ID, "string constant")
+yy(0, 37, 13, MOD, bittree,'%', "%")
+yy(0, 38, 8, BAND, bittree,ID, "&")
+xx(INCR, 39, 0, ADD, addtree,ID, "++")
+yy(0, 40, 0, 0, 0, ID, "(")
+yy(0, 41, 0, 0, 0, ')', ")")
+yy(0, 42, 13, MUL, multree,ID, "*")
+yy(0, 43, 12, ADD, addtree,ID, "+")
+yy(0, 44, 1, 0, 0, ',', ",")
+yy(0, 45, 12, SUB, subtree,ID, "-")
+yy(0, 46, 0, 0, 0, '.', ".")
+yy(0, 47, 13, DIV, multree,'/', "/")
+xx(DECR, 48, 0, SUB, subtree,ID, "--")
+xx(DEREF, 49, 0, 0, 0, DEREF, "->")
+xx(ANDAND, 50, 5, AND, andtree,ANDAND, "&&")
+xx(OROR, 51, 4, OR, andtree,OROR, "||")
+xx(LEQ, 52, 10, LE, cmptree,LEQ, "<=")
+xx(EQL, 53, 9, EQ, eqtree, EQL, "==")
+xx(NEQ, 54, 9, NE, eqtree, NEQ, "!=")
+xx(GEQ, 55, 10, GE, cmptree,GEQ, ">=")
+xx(RSHIFT, 56, 11, RSH, shtree, RSHIFT, ">>")
+xx(LSHIFT, 57, 11, LSH, shtree, LSHIFT, "<<")
+yy(0, 58, 0, 0, 0, ':', ":")
+yy(0, 59, 0, 0, 0, IF, ";")
+yy(0, 60, 10, LT, cmptree,'<', "<")
+yy(0, 61, 2, ASGN, asgntree,'=', "=")
+yy(0, 62, 10, GT, cmptree,'>', ">")
+yy(0, 63, 0, 0, 0, '?', "?")
+xx(ELLIPSIS, 64, 0, 0, 0, ELLIPSIS,"...")
+xx(SIZEOF, 65, 0, 0, 0, ID, "sizeof")
+yy(0, 66, 0, 0, 0, 0, 0)
+xx(AUTO, 67, 0, 0, 0, STATIC, "auto")
+xx(BREAK, 68, 0, 0, 0, IF, "break")
+xx(CASE, 69, 0, 0, 0, IF, "case")
+xx(CONTINUE, 70, 0, 0, 0, IF, "continue")
+xx(DEFAULT, 71, 0, 0, 0, IF, "default")
+xx(DO, 72, 0, 0, 0, IF, "do")
+xx(ELSE, 73, 0, 0, 0, IF, "else")
+xx(EXTERN, 74, 0, 0, 0, STATIC, "extern")
+xx(FOR, 75, 0, 0, 0, IF, "for")
+xx(GOTO, 76, 0, 0, 0, IF, "goto")
+xx(IF, 77, 0, 0, 0, IF, "if")
+xx(REGISTER, 78, 0, 0, 0, STATIC, "register")
+xx(RETURN, 79, 0, 0, 0, IF, "return")
+xx(SIGNED, 80, 0, 0, 0, CHAR, "signed")
+xx(STATIC, 81, 0, 0, 0, STATIC, "static")
+xx(SWITCH, 82, 0, 0, 0, IF, "switch")
+xx(TYPEDEF, 83, 0, 0, 0, STATIC, "typedef")
+xx(WHILE, 84, 0, 0, 0, IF, "while")
+xx(TYPECODE, 85, 0, 0, 0, ID, "__typecode")
+xx(FIRSTARG, 86, 0, 0, 0, ID, "__firstarg")
+yy(0, 87, 0, 0, 0, 0, 0)
+yy(0, 88, 0, 0, 0, 0, 0)
+yy(0, 89, 0, 0, 0, 0, 0)
+yy(0, 90, 0, 0, 0, 0, 0)
+yy(0, 91, 0, 0, 0, '[', "[")
+yy(0, 92, 0, 0, 0, 0, 0)
+yy(0, 93, 0, 0, 0, ']', "]")
+yy(0, 94, 7, BXOR, bittree,'^', "^")
+yy(0, 95, 0, 0, 0, 0, 0)
+yy(0, 96, 0, 0, 0, 0, 0)
+yy(0, 97, 0, 0, 0, 0, 0)
+yy(0, 98, 0, 0, 0, 0, 0)
+yy(0, 99, 0, 0, 0, 0, 0)
+yy(0, 100, 0, 0, 0, 0, 0)
+yy(0, 101, 0, 0, 0, 0, 0)
+yy(0, 102, 0, 0, 0, 0, 0)
+yy(0, 103, 0, 0, 0, 0, 0)
+yy(0, 104, 0, 0, 0, 0, 0)
+yy(0, 105, 0, 0, 0, 0, 0)
+yy(0, 106, 0, 0, 0, 0, 0)
+yy(0, 107, 0, 0, 0, 0, 0)
+yy(0, 108, 0, 0, 0, 0, 0)
+yy(0, 109, 0, 0, 0, 0, 0)
+yy(0, 110, 0, 0, 0, 0, 0)
+yy(0, 111, 0, 0, 0, 0, 0)
+yy(0, 112, 0, 0, 0, 0, 0)
+yy(0, 113, 0, 0, 0, 0, 0)
+yy(0, 114, 0, 0, 0, 0, 0)
+yy(0, 115, 0, 0, 0, 0, 0)
+yy(0, 116, 0, 0, 0, 0, 0)
+yy(0, 117, 0, 0, 0, 0, 0)
+yy(0, 118, 0, 0, 0, 0, 0)
+yy(0, 119, 0, 0, 0, 0, 0)
+yy(0, 120, 0, 0, 0, 0, 0)
+yy(0, 121, 0, 0, 0, 0, 0)
+yy(0, 122, 0, 0, 0, 0, 0)
+yy(0, 123, 0, 0, 0, IF, "{")
+yy(0, 124, 6, BOR, bittree,'|', "|")
+yy(0, 125, 0, 0, 0, '}', "}")
+yy(0, 126, 0, BCOM, 0, ID, "~")
+xx(EOI, 127, 0, 0, 0, EOI, "end of input")
+#undef xx
+#undef yy
diff --git a/src/tools/lcc/src/trace.c b/src/tools/lcc/src/trace.c
new file mode 100644
index 0000000..3b9ba78
--- /dev/null
+++ b/src/tools/lcc/src/trace.c
@@ -0,0 +1,181 @@
+#include "c.h"
+
+
+static char *fmt, *fp, *fmtend; /* format string, current & limit pointer */
+static Tree args; /* printf arguments */
+static Symbol frameno; /* local holding frame number */
+
+/* appendstr - append str to the evolving format string, expanding it if necessary */
+static void appendstr(char *str) {
+ do
+ if (fp == fmtend) {
+ if (fp) {
+ char *s = allocate(2*(fmtend - fmt), FUNC);
+ strncpy(s, fmt, fmtend - fmt);
+ fp = s + (fmtend - fmt);
+ fmtend = s + 2*(fmtend - fmt);
+ fmt = s;
+ } else {
+ fp = fmt = allocate(80, FUNC);
+ fmtend = fmt + 80;
+ }
+ }
+ while ((*fp++ = *str++) != 0);
+ fp--;
+}
+
+/* tracevalue - append format and argument to print the value of e */
+static void tracevalue(Tree e, int lev) {
+ Type ty = unqual(e->type);
+
+ switch (ty->op) {
+ case INT:
+ if (ty == chartype || ty == signedchar)
+ appendstr("'\\x%02x'");
+ else if (ty == longtype)
+ appendstr("0x%ld");
+ else
+ appendstr("0x%d");
+ break;
+ case UNSIGNED:
+ if (ty == chartype || ty == unsignedchar)
+ appendstr("'\\x%02x'");
+ else if (ty == unsignedlong)
+ appendstr("0x%lx");
+ else
+ appendstr("0x%x");
+ break;
+ case FLOAT:
+ if (ty == longdouble)
+ appendstr("%Lg");
+ else
+ appendstr("%g");
+ break;
+ case POINTER:
+ if (unqual(ty->type) == chartype
+ || unqual(ty->type) == signedchar
+ || unqual(ty->type) == unsignedchar) {
+ static Symbol null;
+ if (null == NULL)
+ null = mkstr("(null)");
+ tracevalue(cast(e, unsignedtype), lev + 1);
+ appendstr(" \"%.30s\"");
+ e = condtree(e, e, pointer(idtree(null->u.c.loc)));
+ } else {
+ appendstr("("); appendstr(typestring(ty, "")); appendstr(")0x%x");
+ }
+ break;
+ case STRUCT: {
+ Field q;
+ appendstr("("); appendstr(typestring(ty, "")); appendstr("){");
+ for (q = ty->u.sym->u.s.flist; q; q = q->link) {
+ appendstr(q->name); appendstr("=");
+ tracevalue(field(addrof(e), q->name), lev + 1);
+ if (q->link)
+ appendstr(",");
+ }
+ appendstr("}");
+ return;
+ }
+ case UNION:
+ appendstr("("); appendstr(typestring(ty, "")); appendstr("){...}");
+ return;
+ case ARRAY:
+ if (lev && ty->type->size > 0) {
+ int i;
+ e = pointer(e);
+ appendstr("{");
+ for (i = 0; i < ty->size/ty->type->size; i++) {
+ Tree p = (*optree['+'])(ADD, e, consttree(i, inttype));
+ if (isptr(p->type) && isarray(p->type->type))
+ p = retype(p, p->type->type);
+ else
+ p = rvalue(p);
+ if (i)
+ appendstr(",");
+ tracevalue(p, lev + 1);
+ }
+ appendstr("}");
+ } else
+ appendstr(typestring(ty, ""));
+ return;
+ default:
+ assert(0);
+ }
+ e = cast(e, promote(ty));
+ args = tree(mkop(ARG,e->type), e->type, e, args);
+}
+
+/* tracefinis - complete & generate the trace call to print */
+static void tracefinis(Symbol printer) {
+ Tree *ap;
+ Symbol p;
+
+ *fp = 0;
+ p = mkstr(string(fmt));
+ for (ap = &args; *ap; ap = &(*ap)->kids[1])
+ ;
+ *ap = tree(ARG+P, charptype, pointer(idtree(p->u.c.loc)), 0);
+ walk(calltree(pointer(idtree(printer)), freturn(printer->type), args, NULL), 0, 0);
+ args = 0;
+ fp = fmtend = 0;
+}
+
+/* tracecall - generate code to trace entry to f */
+static void tracecall(Symbol printer, Symbol f) {
+ int i;
+ Symbol counter = genident(STATIC, inttype, GLOBAL);
+
+ defglobal(counter, BSS);
+ (*IR->space)(counter->type->size);
+ frameno = genident(AUTO, inttype, level);
+ addlocal(frameno);
+ appendstr(f->name); appendstr("#");
+ tracevalue(asgn(frameno, incr(INCR, idtree(counter), consttree(1, inttype))), 0);
+ appendstr("(");
+ for (i = 0; f->u.f.callee[i]; i++) {
+ if (i)
+ appendstr(",");
+ appendstr(f->u.f.callee[i]->name); appendstr("=");
+ tracevalue(idtree(f->u.f.callee[i]), 0);
+ }
+ if (variadic(f->type))
+ appendstr(",...");
+ appendstr(") called\n");
+ tracefinis(printer);
+}
+
+/* tracereturn - generate code to trace return e */
+static void tracereturn(Symbol printer, Symbol f, Tree e) {
+ appendstr(f->name); appendstr("#");
+ tracevalue(idtree(frameno), 0);
+ appendstr(" returned");
+ if (freturn(f->type) != voidtype && e) {
+ appendstr(" ");
+ tracevalue(e, 0);
+ }
+ appendstr("\n");
+ tracefinis(printer);
+}
+
+/* trace_init - initialize for tracing */
+void trace_init(int argc, char *argv[]) {
+ int i;
+ static int inited;
+
+ if (inited)
+ return;
+ inited = 1;
+ type_init(argc, argv);
+ if (IR)
+ for (i = 1; i < argc; i++)
+ if (strncmp(argv[i], "-t", 2) == 0 && strchr(argv[i], '=') == NULL) {
+ Symbol printer = mksymbol(EXTERN,
+ argv[i][2] ? &argv[i][2] : "printf",
+ ftype(inttype, ptr(qual(CONST, chartype))));
+ printer->defined = 0;
+ attach((Apply)tracecall, printer, &events.entry);
+ attach((Apply)tracereturn, printer, &events.returns);
+ break;
+ }
+}
diff --git a/src/tools/lcc/src/tree.c b/src/tools/lcc/src/tree.c
new file mode 100644
index 0000000..d2b6a91
--- /dev/null
+++ b/src/tools/lcc/src/tree.c
@@ -0,0 +1,223 @@
+#include "c.h"
+
+
+int where = STMT;
+static int warn;
+static int nid = 1; /* identifies trees & nodes in debugging output */
+static struct nodeid {
+ int printed;
+ Tree node;
+} ids[500]; /* if ids[i].node == p, then p's id is i */
+
+static void printtree1(Tree, int, int);
+
+Tree tree(int op, Type type, Tree left, Tree right) {
+ Tree p;
+
+ NEW0(p, where);
+ p->op = op;
+ p->type = type;
+ p->kids[0] = left;
+ p->kids[1] = right;
+ return p;
+}
+
+Tree texpr(Tree (*f)(int), int tok, int a) {
+ int save = where;
+ Tree p;
+
+ where = a;
+ p = (*f)(tok);
+ where = save;
+ return p;
+}
+static Tree root1(Tree p) {
+ if (p == NULL)
+ return p;
+ if (p->type == voidtype)
+ warn++;
+ switch (generic(p->op)) {
+ case COND: {
+ Tree q = p->kids[1];
+ assert(q && q->op == RIGHT);
+ if (p->u.sym && q->kids[0] && generic(q->kids[0]->op) == ASGN)
+ q->kids[0] = root1(q->kids[0]->kids[1]);
+ else
+ q->kids[0] = root1(q->kids[0]);
+ if (p->u.sym && q->kids[1] && generic(q->kids[1]->op) == ASGN)
+ q->kids[1] = root1(q->kids[1]->kids[1]);
+ else
+ q->kids[1] = root1(q->kids[1]);
+ p->u.sym = 0;
+ if (q->kids[0] == 0 && q->kids[1] == 0)
+ p = root1(p->kids[0]);
+ }
+ break;
+ case AND: case OR:
+ if ((p->kids[1] = root1(p->kids[1])) == 0)
+ p = root1(p->kids[0]);
+ break;
+ case NOT:
+ if (warn++ == 0)
+ warning("expression with no effect elided\n");
+ return root1(p->kids[0]);
+ case RIGHT:
+ if (p->kids[1] == 0)
+ return root1(p->kids[0]);
+ if (p->kids[0] && p->kids[0]->op == CALL+B
+ && p->kids[1] && p->kids[1]->op == INDIR+B)
+ /* avoid premature release of the CALL+B temporary */
+ return p->kids[0];
+ if (p->kids[0] && p->kids[0]->op == RIGHT
+ && p->kids[1] == p->kids[0]->kids[0])
+ /* de-construct e++ construction */
+ return p->kids[0]->kids[1];
+ p = tree(RIGHT, p->type, root1(p->kids[0]), root1(p->kids[1]));
+ return p->kids[0] || p->kids[1] ? p : (Tree)0;
+ case EQ: case NE: case GT: case GE: case LE: case LT:
+ case ADD: case SUB: case MUL: case DIV: case MOD:
+ case LSH: case RSH: case BAND: case BOR: case BXOR:
+ if (warn++ == 0)
+ warning("expression with no effect elided\n");
+ p = tree(RIGHT, p->type, root1(p->kids[0]), root1(p->kids[1]));
+ return p->kids[0] || p->kids[1] ? p : (Tree)0;
+ case INDIR:
+ if (p->type->size == 0 && unqual(p->type) != voidtype)
+ warning("reference to `%t' elided\n", p->type);
+ if (isptr(p->kids[0]->type) && isvolatile(p->kids[0]->type->type))
+ warning("reference to `volatile %t' elided\n", p->type);
+ /* fall thru */
+ case CVI: case CVF: case CVU: case CVP:
+ case NEG: case BCOM: case FIELD:
+ if (warn++ == 0)
+ warning("expression with no effect elided\n");
+ return root1(p->kids[0]);
+ case ADDRL: case ADDRG: case ADDRF: case CNST:
+ if (needconst)
+ return p;
+ if (warn++ == 0)
+ warning("expression with no effect elided\n");
+ return NULL;
+ case ARG: case ASGN: case CALL: case JUMP: case LABEL:
+ break;
+ default: assert(0);
+ }
+ return p;
+}
+
+Tree root(Tree p) {
+ warn = 0;
+ return root1(p);
+}
+
+char *opname(int op) {
+ static char *opnames[] = {
+ "",
+ "CNST",
+ "ARG",
+ "ASGN",
+ "INDIR",
+ "CVC",
+ "CVD",
+ "CVF",
+ "CVI",
+ "CVP",
+ "CVS",
+ "CVU",
+ "NEG",
+ "CALL",
+ "*LOAD*",
+ "RET",
+ "ADDRG",
+ "ADDRF",
+ "ADDRL",
+ "ADD",
+ "SUB",
+ "LSH",
+ "MOD",
+ "RSH",
+ "BAND",
+ "BCOM",
+ "BOR",
+ "BXOR",
+ "DIV",
+ "MUL",
+ "EQ",
+ "GE",
+ "GT",
+ "LE",
+ "LT",
+ "NE",
+ "JUMP",
+ "LABEL",
+ "AND",
+ "NOT",
+ "OR",
+ "COND",
+ "RIGHT",
+ "FIELD"
+ }, *suffixes[] = {
+ "0", "F", "D", "C", "S", "I", "U", "P", "V", "B",
+ "10","11","12","13","14","15"
+ };
+
+ if (generic(op) >= AND && generic(op) <= FIELD && opsize(op) == 0)
+ return opnames[opindex(op)];
+ return stringf("%s%s%s",
+ opindex(op) > 0 && opindex(op) < NELEMS(opnames) ?
+ opnames[opindex(op)] : stringd(opindex(op)),
+ suffixes[optype(op)], opsize(op) > 0 ? stringd(opsize(op)) : "");
+}
+
+int nodeid(Tree p) {
+ int i = 1;
+
+ ids[nid].node = p;
+ while (ids[i].node != p)
+ i++;
+ if (i == nid)
+ ids[nid++].printed = 0;
+ return i;
+}
+
+/* printed - return pointer to ids[id].printed */
+int *printed(int id) {
+ if (id)
+ return &ids[id].printed;
+ nid = 1;
+ return 0;
+}
+
+/* printtree - print tree p on fd */
+void printtree(Tree p, int fd) {
+ (void)printed(0);
+ printtree1(p, fd, 1);
+}
+
+/* printtree1 - recursively print tree p */
+static void printtree1(Tree p, int fd, int lev) {
+ FILE *f = fd == 1 ? stdout : stderr;
+ int i;
+ static char blanks[] = " ";
+
+ if (p == 0 || *printed(i = nodeid(p)))
+ return;
+ fprint(f, "#%d%S%S", i, blanks, i < 10 ? 2 : i < 100 ? 1 : 0, blanks, lev);
+ fprint(f, "%s %t", opname(p->op), p->type);
+ *printed(i) = 1;
+ for (i = 0; i < NELEMS(p->kids); i++)
+ if (p->kids[i])
+ fprint(f, " #%d", nodeid(p->kids[i]));
+ if (p->op == FIELD && p->u.field)
+ fprint(f, " %s %d..%d", p->u.field->name,
+ fieldsize(p->u.field) + fieldright(p->u.field), fieldright(p->u.field));
+ else if (generic(p->op) == CNST)
+ fprint(f, " %s", vtoa(p->type, p->u.v));
+ else if (p->u.sym)
+ fprint(f, " %s", p->u.sym->name);
+ if (p->node)
+ fprint(f, " node=%p", p->node);
+ fprint(f, "\n");
+ for (i = 0; i < NELEMS(p->kids); i++)
+ printtree1(p->kids[i], fd, lev + 1);
+}
diff --git a/src/tools/lcc/src/types.c b/src/tools/lcc/src/types.c
new file mode 100644
index 0000000..4aa3d18
--- /dev/null
+++ b/src/tools/lcc/src/types.c
@@ -0,0 +1,748 @@
+#include "c.h"
+#include <float.h>
+
+
+static Field isfield(const char *, Field);
+static Type type(int, Type, int, int, void *);
+
+static struct entry {
+ struct type type;
+ struct entry *link;
+} *typetable[128];
+static int maxlevel;
+
+static Symbol pointersym;
+
+Type chartype; /* char */
+Type doubletype; /* double */
+Type floattype; /* float */
+Type inttype; /* signed int */
+Type longdouble; /* long double */
+Type longtype; /* long */
+Type longlong; /* long long */
+Type shorttype; /* signed short int */
+Type signedchar; /* signed char */
+Type unsignedchar; /* unsigned char */
+Type unsignedlong; /* unsigned long int */
+Type unsignedlonglong; /* unsigned long long int */
+Type unsignedshort; /* unsigned short int */
+Type unsignedtype; /* unsigned int */
+Type funcptype; /* void (*)() */
+Type charptype; /* char* */
+Type voidptype; /* void* */
+Type voidtype; /* basic types: void */
+Type unsignedptr; /* unsigned type to hold void* */
+Type signedptr; /* signed type to hold void* */
+Type widechar; /* unsigned type that represents wchar_t */
+
+static Type xxinit(int op, char *name, Metrics m) {
+ Symbol p = install(string(name), &types, GLOBAL, PERM);
+ Type ty = type(op, 0, m.size, m.align, p);
+
+ assert(ty->align == 0 || ty->size%ty->align == 0);
+ p->type = ty;
+ p->addressed = m.outofline;
+ switch (ty->op) {
+ case INT:
+ p->u.limits.max.i = ones(8*ty->size)>>1;
+ p->u.limits.min.i = -p->u.limits.max.i - 1;
+ break;
+ case UNSIGNED:
+ p->u.limits.max.u = ones(8*ty->size);
+ p->u.limits.min.u = 0;
+ break;
+ case FLOAT:
+ if (ty->size == sizeof (float))
+ p->u.limits.max.d = FLT_MAX;
+ else if (ty->size == sizeof (double))
+ p->u.limits.max.d = DBL_MAX;
+ else
+ p->u.limits.max.d = LDBL_MAX;
+ p->u.limits.min.d = -p->u.limits.max.d;
+ break;
+ default: assert(0);
+ }
+ return ty;
+}
+static Type type(int op, Type ty, int size, int align, void *sym) {
+ unsigned h = (op^((unsigned long)ty>>3))
+&(NELEMS(typetable)-1);
+ struct entry *tn;
+
+ if (op != FUNCTION && (op != ARRAY || size > 0))
+ for (tn = typetable[h]; tn; tn = tn->link)
+ if (tn->type.op == op && tn->type.type == ty
+ && tn->type.size == size && tn->type.align == align
+ && tn->type.u.sym == sym)
+ return &tn->type;
+ NEW0(tn, PERM);
+ tn->type.op = op;
+ tn->type.type = ty;
+ tn->type.size = size;
+ tn->type.align = align;
+ tn->type.u.sym = sym;
+ tn->link = typetable[h];
+ typetable[h] = tn;
+ return &tn->type;
+}
+void type_init(int argc, char *argv[]) {
+ static int inited;
+ int i;
+
+ if (inited)
+ return;
+ inited = 1;
+ if (!IR)
+ return;
+ for (i = 1; i < argc; i++) {
+ int size, align, outofline;
+ if (strncmp(argv[i], "-unsigned_char=", 15) == 0)
+ IR->unsigned_char = argv[i][15] - '0';
+#define xx(name) \
+ else if (sscanf(argv[i], "-" #name "=%d,%d,%d", &size, &align, &outofline) == 3) { \
+ IR->name.size = size; IR->name.align = align; \
+ IR->name.outofline = outofline; }
+ xx(charmetric)
+ xx(shortmetric)
+ xx(intmetric)
+ xx(longmetric)
+ xx(longlongmetric)
+ xx(floatmetric)
+ xx(doublemetric)
+ xx(longdoublemetric)
+ xx(ptrmetric)
+ xx(structmetric)
+#undef xx
+ }
+#define xx(v,name,op,metrics) v=xxinit(op,name,IR->metrics)
+ xx(chartype, "char", IR->unsigned_char ? UNSIGNED : INT,charmetric);
+ xx(doubletype, "double", FLOAT, doublemetric);
+ xx(floattype, "float", FLOAT, floatmetric);
+ xx(inttype, "int", INT, intmetric);
+ xx(longdouble, "long double", FLOAT, longdoublemetric);
+ xx(longtype, "long int", INT, longmetric);
+ xx(longlong, "long long int", INT, longlongmetric);
+ xx(shorttype, "short", INT, shortmetric);
+ xx(signedchar, "signed char", INT, charmetric);
+ xx(unsignedchar, "unsigned char", UNSIGNED,charmetric);
+ xx(unsignedlong, "unsigned long", UNSIGNED,longmetric);
+ xx(unsignedshort, "unsigned short", UNSIGNED,shortmetric);
+ xx(unsignedtype, "unsigned int", UNSIGNED,intmetric);
+ xx(unsignedlonglong,"unsigned long long",UNSIGNED,longlongmetric);
+#undef xx
+ {
+ Symbol p;
+ p = install(string("void"), &types, GLOBAL, PERM);
+ voidtype = type(VOID, NULL, 0, 0, p);
+ p->type = voidtype;
+ }
+ pointersym = install(string("T*"), &types, GLOBAL, PERM);
+ pointersym->addressed = IR->ptrmetric.outofline;
+ pointersym->u.limits.max.p = (void*)ones(8*IR->ptrmetric.size);
+ pointersym->u.limits.min.p = 0;
+ voidptype = ptr(voidtype);
+ funcptype = ptr(func(voidtype, NULL, 1));
+ charptype = ptr(chartype);
+#define xx(v,t) if (v==NULL && t->size==voidptype->size && t->align==voidptype->align) v=t
+ xx(unsignedptr,unsignedshort);
+ xx(unsignedptr,unsignedtype);
+ xx(unsignedptr,unsignedlong);
+ xx(unsignedptr,unsignedlonglong);
+ if (unsignedptr == NULL)
+ unsignedptr = type(UNSIGNED, NULL, voidptype->size, voidptype->align, voidptype->u.sym);
+ xx(signedptr,shorttype);
+ xx(signedptr,inttype);
+ xx(signedptr,longtype);
+ xx(signedptr,longlong);
+ if (signedptr == NULL)
+ signedptr = type(INT, NULL, voidptype->size, voidptype->align, voidptype->u.sym);
+#undef xx
+ widechar = unsignedshort;
+ for (i = 0; i < argc; i++) {
+#define xx(name,type) \
+ if (strcmp(argv[i], "-wchar_t=" #name) == 0) \
+ widechar = type;
+ xx(unsigned_char,unsignedchar)
+ xx(unsigned_int,unsignedtype)
+ xx(unsigned_short,unsignedshort)
+ }
+#undef xx
+}
+void rmtypes(int lev) {
+ if (maxlevel >= lev) {
+ int i;
+ maxlevel = 0;
+ for (i = 0; i < NELEMS(typetable); i++) {
+ struct entry *tn, **tq = &typetable[i];
+ while ((tn = *tq) != NULL)
+ if (tn->type.op == FUNCTION)
+ tq = &tn->link;
+ else if (tn->type.u.sym && tn->type.u.sym->scope >= lev)
+ *tq = tn->link;
+ else {
+ if (tn->type.u.sym && tn->type.u.sym->scope > maxlevel)
+ maxlevel = tn->type.u.sym->scope;
+ tq = &tn->link;
+ }
+
+ }
+ }
+}
+Type ptr(Type ty) {
+ return type(POINTER, ty, IR->ptrmetric.size,
+ IR->ptrmetric.align, pointersym);
+}
+Type deref(Type ty) {
+ if (isptr(ty))
+ ty = ty->type;
+ else
+ error("type error: %s\n", "pointer expected");
+ return isenum(ty) ? unqual(ty)->type : ty;
+}
+Type array(Type ty, int n, int a) {
+ assert(ty);
+ if (isfunc(ty)) {
+ error("illegal type `array of %t'\n", ty);
+ return array(inttype, n, 0);
+ }
+ if (isarray(ty) && ty->size == 0)
+ error("missing array size\n");
+ if (ty->size == 0) {
+ if (unqual(ty) == voidtype)
+ error("illegal type `array of %t'\n", ty);
+ else if (Aflag >= 2)
+ warning("declaring type array of %t' is undefined\n", ty);
+
+ } else if (n > INT_MAX/ty->size) {
+ error("size of `array of %t' exceeds %d bytes\n",
+ ty, INT_MAX);
+ n = 1;
+ }
+ return type(ARRAY, ty, n*ty->size,
+ a ? a : ty->align, NULL);
+}
+Type atop(Type ty) {
+ if (isarray(ty))
+ return ptr(ty->type);
+ error("type error: %s\n", "array expected");
+ return ptr(ty);
+}
+Type qual(int op, Type ty) {
+ if (isarray(ty))
+ ty = type(ARRAY, qual(op, ty->type), ty->size,
+ ty->align, NULL);
+ else if (isfunc(ty))
+ warning("qualified function type ignored\n");
+ else if ((isconst(ty) && op == CONST)
+ || (isvolatile(ty) && op == VOLATILE))
+ error("illegal type `%k %t'\n", op, ty);
+ else {
+ if (isqual(ty)) {
+ op += ty->op;
+ ty = ty->type;
+ }
+ ty = type(op, ty, ty->size, ty->align, NULL);
+ }
+ return ty;
+}
+Type func(Type ty, Type *proto, int style) {
+ if (ty && (isarray(ty) || isfunc(ty)))
+ error("illegal return type `%t'\n", ty);
+ ty = type(FUNCTION, ty, 0, 0, NULL);
+ ty->u.f.proto = proto;
+ ty->u.f.oldstyle = style;
+ return ty;
+}
+Type freturn(Type ty) {
+ if (isfunc(ty))
+ return ty->type;
+ error("type error: %s\n", "function expected");
+ return inttype;
+}
+int variadic(Type ty) {
+ if (isfunc(ty) && ty->u.f.proto) {
+ int i;
+ for (i = 0; ty->u.f.proto[i]; i++)
+ ;
+ return i > 1 && ty->u.f.proto[i-1] == voidtype;
+ }
+ return 0;
+}
+Type newstruct(int op, char *tag) {
+ Symbol p;
+
+ assert(tag);
+ if (*tag == 0)
+ tag = stringd(genlabel(1));
+ else
+ if ((p = lookup(tag, types)) != NULL && (p->scope == level
+ || (p->scope == PARAM && level == PARAM+1))) {
+ if (p->type->op == op && !p->defined)
+ return p->type;
+ error("redefinition of `%s' previously defined at %w\n",
+ p->name, &p->src);
+ }
+ p = install(tag, &types, level, PERM);
+ p->type = type(op, NULL, 0, 0, p);
+ if (p->scope > maxlevel)
+ maxlevel = p->scope;
+ p->src = src;
+ return p->type;
+}
+Field newfield(char *name, Type ty, Type fty) {
+ Field p, *q = &ty->u.sym->u.s.flist;
+
+ if (name == NULL)
+ name = stringd(genlabel(1));
+ for (p = *q; p; q = &p->link, p = *q)
+ if (p->name == name)
+ error("duplicate field name `%s' in `%t'\n",
+ name, ty);
+ NEW0(p, PERM);
+ *q = p;
+ p->name = name;
+ p->type = fty;
+ if (xref) { /* omit */
+ if (ty->u.sym->u.s.ftab == NULL) /* omit */
+ ty->u.sym->u.s.ftab = table(NULL, level); /* omit */
+ install(name, &ty->u.sym->u.s.ftab, 0, PERM)->src = src;/* omit */
+ } /* omit */
+ return p;
+}
+int eqtype(Type ty1, Type ty2, int ret) {
+ if (ty1 == ty2)
+ return 1;
+ if (ty1->op != ty2->op)
+ return 0;
+ switch (ty1->op) {
+ case ENUM: case UNION: case STRUCT:
+ case UNSIGNED: case INT: case FLOAT:
+ return 0;
+ case POINTER: return eqtype(ty1->type, ty2->type, 1);
+ case VOLATILE: case CONST+VOLATILE:
+ case CONST: return eqtype(ty1->type, ty2->type, 1);
+ case ARRAY: if (eqtype(ty1->type, ty2->type, 1)) {
+ if (ty1->size == ty2->size)
+ return 1;
+ if (ty1->size == 0 || ty2->size == 0)
+ return ret;
+ }
+ return 0;
+ case FUNCTION: if (eqtype(ty1->type, ty2->type, 1)) {
+ Type *p1 = ty1->u.f.proto, *p2 = ty2->u.f.proto;
+ if (p1 == p2)
+ return 1;
+ if (p1 && p2) {
+ for ( ; *p1 && *p2; p1++, p2++)
+ if (eqtype(unqual(*p1), unqual(*p2), 1) == 0)
+ return 0;
+ if (*p1 == NULL && *p2 == NULL)
+ return 1;
+ } else {
+ if (variadic(p1 ? ty1 : ty2))
+ return 0;
+ if (p1 == NULL)
+ p1 = p2;
+ for ( ; *p1; p1++) {
+ Type ty = unqual(*p1);
+ if (promote(ty) != (isenum(ty) ? ty->type : ty))
+ return 0;
+ }
+ return 1;
+ }
+ }
+ return 0;
+ }
+ assert(0); return 0;
+}
+Type promote(Type ty) {
+ ty = unqual(ty);
+ switch (ty->op) {
+ case ENUM:
+ return inttype;
+ case INT:
+ if (ty->size < inttype->size)
+ return inttype;
+ break;
+ case UNSIGNED:
+ if (ty->size < inttype->size)
+ return inttype;
+ if (ty->size < unsignedtype->size)
+ return unsignedtype;
+ break;
+ case FLOAT:
+ if (ty->size < doubletype->size)
+ return doubletype;
+ }
+ return ty;
+}
+Type signedint(Type ty) {
+ if (ty->op == INT)
+ return ty;
+ assert(ty->op == UNSIGNED);
+#define xx(t) if (ty->size == t->size) return t
+ xx(inttype);
+ xx(longtype);
+ xx(longlong);
+#undef xx
+ assert(0); return NULL;
+}
+Type compose(Type ty1, Type ty2) {
+ if (ty1 == ty2)
+ return ty1;
+ assert(ty1->op == ty2->op);
+ switch (ty1->op) {
+ case POINTER:
+ return ptr(compose(ty1->type, ty2->type));
+ case CONST+VOLATILE:
+ return qual(CONST, qual(VOLATILE,
+ compose(ty1->type, ty2->type)));
+ case CONST: case VOLATILE:
+ return qual(ty1->op, compose(ty1->type, ty2->type));
+ case ARRAY: { Type ty = compose(ty1->type, ty2->type);
+ if (ty1->size && ((ty1->type->size && ty2->size == 0) || ty1->size == ty2->size))
+ return array(ty, ty1->size/ty1->type->size, ty1->align);
+ if (ty2->size && ty2->type->size && ty1->size == 0)
+ return array(ty, ty2->size/ty2->type->size, ty2->align);
+ return array(ty, 0, 0); }
+ case FUNCTION: { Type *p1 = ty1->u.f.proto, *p2 = ty2->u.f.proto;
+ Type ty = compose(ty1->type, ty2->type);
+ List tlist = NULL;
+ if (p1 == NULL && p2 == NULL)
+ return func(ty, NULL, 1);
+ if (p1 && p2 == NULL)
+ return func(ty, p1, ty1->u.f.oldstyle);
+ if (p2 && p1 == NULL)
+ return func(ty, p2, ty2->u.f.oldstyle);
+ for ( ; *p1 && *p2; p1++, p2++) {
+ Type ty = compose(unqual(*p1), unqual(*p2));
+ if (isconst(*p1) || isconst(*p2))
+ ty = qual(CONST, ty);
+ if (isvolatile(*p1) || isvolatile(*p2))
+ ty = qual(VOLATILE, ty);
+ tlist = append(ty, tlist);
+ }
+ assert(*p1 == NULL && *p2 == NULL);
+ return func(ty, ltov(&tlist, PERM), 0); }
+ }
+ assert(0); return NULL;
+}
+int ttob(Type ty) {
+ switch (ty->op) {
+ case CONST: case VOLATILE: case CONST+VOLATILE:
+ return ttob(ty->type);
+ case VOID: case INT: case UNSIGNED: case FLOAT:
+ return ty->op + sizeop(ty->size);
+ case POINTER:
+ return POINTER + sizeop(voidptype->size);
+ case FUNCTION:
+ return POINTER + sizeop(funcptype->size);
+ case ARRAY: case STRUCT: case UNION:
+ return STRUCT;
+ case ENUM:
+ return INT + sizeop(inttype->size);
+ }
+ assert(0); return INT;
+}
+Type btot(int op, int size) {
+#define xx(ty) if (size == (ty)->size) return ty;
+ switch (optype(op)) {
+ case F:
+ xx(floattype);
+ xx(doubletype);
+ xx(longdouble);
+ assert(0); return 0;
+ case I:
+ if (chartype->op == INT)
+ xx(chartype);
+ xx(signedchar);
+ xx(shorttype);
+ xx(inttype);
+ xx(longtype);
+ xx(longlong);
+ assert(0); return 0;
+ case U:
+ if (chartype->op == UNSIGNED)
+ xx(chartype);
+ xx(unsignedchar);
+ xx(unsignedshort);
+ xx(unsignedtype);
+ xx(unsignedlong);
+ xx(unsignedlonglong);
+ assert(0); return 0;
+ case P:
+ xx(voidptype);
+ xx(funcptype);
+ assert(0); return 0;
+ }
+#undef xx
+ assert(0); return 0;
+}
+int hasproto(Type ty) {
+ if (ty == 0)
+ return 1;
+ switch (ty->op) {
+ case CONST: case VOLATILE: case CONST+VOLATILE: case POINTER:
+ case ARRAY:
+ return hasproto(ty->type);
+ case FUNCTION:
+ return hasproto(ty->type) && ty->u.f.proto;
+ case STRUCT: case UNION:
+ case VOID: case FLOAT: case ENUM: case INT: case UNSIGNED:
+ return 1;
+ }
+ assert(0); return 0;
+}
+/* fieldlist - construct a flat list of fields in type ty */
+Field fieldlist(Type ty) {
+ return ty->u.sym->u.s.flist;
+}
+
+/* fieldref - find field name of type ty, return entry */
+Field fieldref(const char *name, Type ty) {
+ Field p = isfield(name, unqual(ty)->u.sym->u.s.flist);
+
+ if (p && xref) {
+ Symbol q;
+ assert(unqual(ty)->u.sym->u.s.ftab);
+ q = lookup(name, unqual(ty)->u.sym->u.s.ftab);
+ assert(q);
+ use(q, src);
+ }
+ return p;
+}
+
+/* ftype - return a function type for rty function (ty,...)' */
+Type ftype(Type rty, Type ty) {
+ List list = append(ty, NULL);
+
+ list = append(voidtype, list);
+ return func(rty, ltov(&list, PERM), 0);
+}
+
+/* isfield - if name is a field in flist, return pointer to the field structure */
+static Field isfield(const char *name, Field flist) {
+ for ( ; flist; flist = flist->link)
+ if (flist->name == name)
+ break;
+ return flist;
+}
+
+/* outtype - output type ty */
+void outtype(Type ty, FILE *f) {
+ switch (ty->op) {
+ case CONST+VOLATILE: case CONST: case VOLATILE:
+ fprint(f, "%k %t", ty->op, ty->type);
+ break;
+ case STRUCT: case UNION: case ENUM:
+ assert(ty->u.sym);
+ if (ty->size == 0)
+ fprint(f, "incomplete ");
+ assert(ty->u.sym->name);
+ if (*ty->u.sym->name >= '1' && *ty->u.sym->name <= '9') {
+ Symbol p = findtype(ty);
+ if (p == 0)
+ fprint(f, "%k defined at %w", ty->op, &ty->u.sym->src);
+ else
+ fprint(f, p->name);
+ } else {
+ fprint(f, "%k %s", ty->op, ty->u.sym->name);
+ if (ty->size == 0)
+ fprint(f, " defined at %w", &ty->u.sym->src);
+ }
+ break;
+ case VOID: case FLOAT: case INT: case UNSIGNED:
+ fprint(f, ty->u.sym->name);
+ break;
+ case POINTER:
+ fprint(f, "pointer to %t", ty->type);
+ break;
+ case FUNCTION:
+ fprint(f, "%t function", ty->type);
+ if (ty->u.f.proto && ty->u.f.proto[0]) {
+ int i;
+ fprint(f, "(%t", ty->u.f.proto[0]);
+ for (i = 1; ty->u.f.proto[i]; i++)
+ if (ty->u.f.proto[i] == voidtype)
+ fprint(f, ",...");
+ else
+ fprint(f, ",%t", ty->u.f.proto[i]);
+ fprint(f, ")");
+ } else if (ty->u.f.proto && ty->u.f.proto[0] == 0)
+ fprint(f, "(void)");
+
+ break;
+ case ARRAY:
+ if (ty->size > 0 && ty->type && ty->type->size > 0) {
+ fprint(f, "array %d", ty->size/ty->type->size);
+ while (ty->type && isarray(ty->type) && ty->type->type->size > 0) {
+ ty = ty->type;
+ fprint(f, ",%d", ty->size/ty->type->size);
+ }
+ } else
+ fprint(f, "incomplete array");
+ if (ty->type)
+ fprint(f, " of %t", ty->type);
+ break;
+ default: assert(0);
+ }
+}
+
+/* printdecl - output a C declaration for symbol p of type ty */
+void printdecl(Symbol p, Type ty) {
+ switch (p->sclass) {
+ case AUTO:
+ fprint(stderr, "%s;\n", typestring(ty, p->name));
+ break;
+ case STATIC: case EXTERN:
+ fprint(stderr, "%k %s;\n", p->sclass, typestring(ty, p->name));
+ break;
+ case TYPEDEF: case ENUM:
+ break;
+ default: assert(0);
+ }
+}
+
+/* printproto - output a prototype declaration for function p */
+void printproto(Symbol p, Symbol callee[]) {
+ if (p->type->u.f.proto)
+ printdecl(p, p->type);
+ else {
+ int i;
+ List list = 0;
+ if (callee[0] == 0)
+ list = append(voidtype, list);
+ else
+ for (i = 0; callee[i]; i++)
+ list = append(callee[i]->type, list);
+ printdecl(p, func(freturn(p->type), ltov(&list, PERM), 0));
+ }
+}
+
+/* prtype - print details of type ty on f with given indent */
+static void prtype(Type ty, FILE *f, int indent, unsigned mark) {
+ switch (ty->op) {
+ default:
+ fprint(f, "(%d %d %d [%p])", ty->op, ty->size, ty->align, ty->u.sym);
+ break;
+ case FLOAT: case INT: case UNSIGNED: case VOID:
+ fprint(f, "(%k %d %d [\"%s\"])", ty->op, ty->size, ty->align, ty->u.sym->name);
+ break;
+ case CONST+VOLATILE: case CONST: case VOLATILE: case POINTER: case ARRAY:
+ fprint(f, "(%k %d %d ", ty->op, ty->size, ty->align);
+ prtype(ty->type, f, indent+1, mark);
+ fprint(f, ")");
+ break;
+ case STRUCT: case UNION:
+ fprint(f, "(%k %d %d [\"%s\"]", ty->op, ty->size, ty->align, ty->u.sym->name);
+ if (ty->x.marked != mark) {
+ Field p;
+ ty->x.marked = mark;
+ for (p = ty->u.sym->u.s.flist; p; p = p->link) {
+ fprint(f, "\n%I", indent+1);
+ prtype(p->type, f, indent+1, mark);
+ fprint(f, " %s@%d", p->name, p->offset);
+ if (p->lsb)
+ fprint(f, ":%d..%d",
+ fieldsize(p) + fieldright(p), fieldright(p));
+ }
+ fprint(f, "\n%I", indent);
+ }
+ fprint(f, ")");
+ break;
+ case ENUM:
+ fprint(f, "(%k %d %d [\"%s\"]", ty->op, ty->size, ty->align, ty->u.sym->name);
+ if (ty->x.marked != mark) {
+ int i;
+ Symbol *p = ty->u.sym->u.idlist;
+ ty->x.marked = mark;
+ for (i = 0; p[i] != NULL; i++)
+ fprint(f, "%I%s=%d\n", indent+1, p[i]->name, p[i]->u.value);
+ }
+ fprint(f, ")");
+ break;
+ case FUNCTION:
+ fprint(f, "(%k %d %d ", ty->op, ty->size, ty->align);
+ prtype(ty->type, f, indent+1, mark);
+ if (ty->u.f.proto) {
+ int i;
+ fprint(f, "\n%I{", indent+1);
+ for (i = 0; ty->u.f.proto[i]; i++) {
+ if (i > 0)
+ fprint(f, "%I", indent+2);
+ prtype(ty->u.f.proto[i], f, indent+2, mark);
+ fprint(f, "\n");
+ }
+ fprint(f, "%I}", indent+1);
+ }
+ fprint(f, ")");
+ break;
+ }
+}
+
+/* printtype - print details of type ty on fd */
+void printtype(Type ty, int fd) {
+ static unsigned mark;
+ prtype(ty, fd == 1 ? stdout : stderr, 0, ++mark);
+ fprint(fd == 1 ? stdout : stderr, "\n");
+}
+
+/* typestring - return ty as C declaration for str, which may be "" */
+char *typestring(Type ty, char *str) {
+ for ( ; ty; ty = ty->type) {
+ Symbol p;
+ switch (ty->op) {
+ case CONST+VOLATILE: case CONST: case VOLATILE:
+ if (isptr(ty->type))
+ str = stringf("%k %s", ty->op, str);
+ else
+ return stringf("%k %s", ty->op, typestring(ty->type, str));
+ break;
+ case STRUCT: case UNION: case ENUM:
+ assert(ty->u.sym);
+ if ((p = findtype(ty)) != NULL)
+ return *str ? stringf("%s %s", p->name, str) : p->name;
+ if (*ty->u.sym->name >= '1' && *ty->u.sym->name <= '9')
+ warning("unnamed %k in prototype\n", ty->op);
+ if (*str)
+ return stringf("%k %s %s", ty->op, ty->u.sym->name, str);
+ else
+ return stringf("%k %s", ty->op, ty->u.sym->name);
+ case VOID: case FLOAT: case INT: case UNSIGNED:
+ return *str ? stringf("%s %s", ty->u.sym->name, str) : ty->u.sym->name;
+ case POINTER:
+ if (!ischar(ty->type) && (p = findtype(ty)) != NULL)
+ return *str ? stringf("%s %s", p->name, str) : p->name;
+ str = stringf(isarray(ty->type) || isfunc(ty->type) ? "(*%s)" : "*%s", str);
+ break;
+ case FUNCTION:
+ if ((p = findtype(ty)) != NULL)
+ return *str ? stringf("%s %s", p->name, str) : p->name;
+ if (ty->u.f.proto == 0)
+ str = stringf("%s()", str);
+ else if (ty->u.f.proto[0]) {
+ int i;
+ str = stringf("%s(%s", str, typestring(ty->u.f.proto[0], ""));
+ for (i = 1; ty->u.f.proto[i]; i++)
+ if (ty->u.f.proto[i] == voidtype)
+ str = stringf("%s, ...", str);
+ else
+ str = stringf("%s, %s", str, typestring(ty->u.f.proto[i], ""));
+ str = stringf("%s)", str);
+ } else
+ str = stringf("%s(void)", str);
+ break;
+ case ARRAY:
+ if ((p = findtype(ty)) != NULL)
+ return *str ? stringf("%s %s", p->name, str) : p->name;
+ if (ty->type && ty->type->size > 0)
+ str = stringf("%s[%d]", str, ty->size/ty->type->size);
+ else
+ str = stringf("%s[]", str);
+ break;
+ default: assert(0);
+ }
+ }
+ assert(0); return 0;
+}
+
diff --git a/src/ui/menudef.h b/src/ui/menudef.h
new file mode 100644
index 0000000..dbd0996
--- /dev/null
+++ b/src/ui/menudef.h
@@ -0,0 +1,363 @@
+
+#define ITEM_TYPE_TEXT 0 // simple text
+#define ITEM_TYPE_BUTTON 1 // button, basically text with a border
+#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped
+#define ITEM_TYPE_CHECKBOX 3 // check box
+#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar
+#define ITEM_TYPE_SAYFIELD 5 // the chat field
+#define ITEM_TYPE_COMBO 6 // drop down list
+#define ITEM_TYPE_LISTBOX 7 // scrollable list
+#define ITEM_TYPE_MODEL 8 // model
+#define ITEM_TYPE_OWNERDRAW 9 // owner draw, name specs what it is
+#define ITEM_TYPE_NUMERICFIELD 10 // editable text, associated with a cvar
+#define ITEM_TYPE_SLIDER 11 // mouse speed, volume, etc.
+#define ITEM_TYPE_YESNO 12 // yes no cvar setting
+#define ITEM_TYPE_MULTI 13 // multiple list setting, enumerated
+#define ITEM_TYPE_BIND 14 // multiple list setting, enumerated
+
+#define ITEM_ALIGN_LEFT 0 // left alignment
+#define ITEM_ALIGN_CENTER 1 // center alignment
+#define ITEM_ALIGN_RIGHT 2 // right alignment
+
+#define ITEM_TEXTSTYLE_NORMAL 0 // normal text
+#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking
+#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing
+#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_NEON 7 // drop shadow ( need a color for this )
+
+#define WINDOW_BORDER_NONE 0 // no border
+#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel )
+#define WINDOW_BORDER_HORZ 2 // horizontal borders only
+#define WINDOW_BORDER_VERT 3 // vertical borders only
+#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars
+
+#define WINDOW_STYLE_EMPTY 0 // no background
+#define WINDOW_STYLE_FILLED 1 // filled with background color
+#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color
+#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color
+#define WINDOW_STYLE_TEAMCOLOR 4 // team color
+#define WINDOW_STYLE_CINEMATIC 5 // cinematic
+
+#define MENU_TRUE 1 // uh.. true
+#define MENU_FALSE 0 // and false
+
+#define HUD_VERTICAL 0x00
+#define HUD_HORIZONTAL 0x01
+
+// list box element types
+#define LISTBOX_TEXT 0x00
+#define LISTBOX_IMAGE 0x01
+
+// list feeders
+#define FEEDER_HEADS 0x00 // model heads
+#define FEEDER_MAPS 0x01 // text maps based on game type
+#define FEEDER_SERVERS 0x02 // servers
+#define FEEDER_CLANS 0x03 // clan names
+#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format
+#define FEEDER_ALIENTEAM_LIST 0x05 // red team members
+#define FEEDER_HUMANTEAM_LIST 0x06 // blue team members
+#define FEEDER_PLAYER_LIST 0x07 // players
+#define FEEDER_TEAM_LIST 0x08 // team members for team voting
+#define FEEDER_MODS 0x09 // team members for team voting
+#define FEEDER_DEMOS 0x0a // team members for team voting
+#define FEEDER_SCOREBOARD 0x0b // team members for team voting
+#define FEEDER_Q3HEADS 0x0c // model heads
+#define FEEDER_SERVERSTATUS 0x0d // server status
+#define FEEDER_FINDPLAYER 0x0e // find player
+#define FEEDER_CINEMATICS 0x0f // cinematics
+
+//TA: tremulous menus
+#define FEEDER_TREMTEAMS 0x10 //teams
+#define FEEDER_TREMALIENCLASSES 0x11 //alien classes
+#define FEEDER_TREMHUMANITEMS 0x12 //human items
+#define FEEDER_TREMHUMANARMOURYBUY 0x13 //human buy
+#define FEEDER_TREMHUMANARMOURYSELL 0x14 //human sell
+#define FEEDER_TREMALIENUPGRADE 0x15 //alien upgrade
+#define FEEDER_TREMALIENBUILD 0x16 //alien buildables
+#define FEEDER_TREMHUMANBUILD 0x17 //human buildables
+//TA: tremulous menus
+#define FEEDER_IGNORE_LIST 0x18 //ignored players
+
+// display flags
+#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001
+#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002
+#define CG_SHOW_ANYTEAMGAME 0x00000004
+#define CG_SHOW_HARVESTER 0x00000008
+#define CG_SHOW_ONEFLAG 0x00000010
+#define CG_SHOW_CTF 0x00000020
+#define CG_SHOW_OBELISK 0x00000040
+#define CG_SHOW_HEALTHCRITICAL 0x00000080
+#define CG_SHOW_SINGLEPLAYER 0x00000100
+#define CG_SHOW_TOURNAMENT 0x00000200
+#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400
+#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800
+#define CG_SHOW_LANPLAYONLY 0x00001000
+#define CG_SHOW_MINED 0x00002000
+#define CG_SHOW_HEALTHOK 0x00004000
+#define CG_SHOW_TEAMINFO 0x00008000
+#define CG_SHOW_NOTEAMINFO 0x00010000
+#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000
+#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000
+#define CG_SHOW_ANYNONTEAMGAME 0x00080000
+#define CG_SHOW_2DONLY 0x10000000
+
+
+#define UI_SHOW_LEADER 0x00000001
+#define UI_SHOW_NOTLEADER 0x00000002
+#define UI_SHOW_FAVORITESERVERS 0x00000004
+#define UI_SHOW_ANYNONTEAMGAME 0x00000008
+#define UI_SHOW_ANYTEAMGAME 0x00000010
+#define UI_SHOW_NEWHIGHSCORE 0x00000020
+#define UI_SHOW_DEMOAVAILABLE 0x00000040
+#define UI_SHOW_NEWBESTTIME 0x00000080
+#define UI_SHOW_FFA 0x00000100
+#define UI_SHOW_NOTFFA 0x00000200
+#define UI_SHOW_NETANYNONTEAMGAME 0x00000400
+#define UI_SHOW_NETANYTEAMGAME 0x00000800
+#define UI_SHOW_NOTFAVORITESERVERS 0x00001000
+
+#define UI_SHOW_VOTEACTIVE 0x00002000
+#define UI_SHOW_CANVOTE 0x00004000
+#define UI_SHOW_TEAMVOTEACTIVE 0x00008000
+#define UI_SHOW_CANTEAMVOTE 0x00010000
+
+#define UI_SHOW_NOTSPECTATING 0x00020000
+
+// owner draw types
+// ideally these should be done outside of this file but
+// this makes it much easier for the macro expansion to
+// convert them for the designers ( from the .menu files )
+#define CG_OWNERDRAW_BASE 1
+#define CG_PLAYER_ARMOR_ICON 1
+#define CG_PLAYER_ARMOR_VALUE 2
+#define CG_PLAYER_HEAD 3
+#define CG_PLAYER_HEALTH 4
+#define CG_PLAYER_HEALTH_BAR 92
+#define CG_PLAYER_HEALTH_CROSS 99
+#define CG_PLAYER_AMMO_ICON 5
+#define CG_PLAYER_AMMO_VALUE 6
+#define CG_PLAYER_CLIPS_VALUE 70
+#define CG_PLAYER_BUILD_TIMER 115
+#define CG_PLAYER_CREDITS_VALUE 71
+#define CG_PLAYER_BANK_VALUE 72
+#define CG_PLAYER_CREDITS_VALUE_NOPAD 106
+#define CG_PLAYER_BANK_VALUE_NOPAD 107
+#define CG_PLAYER_STAMINA 73
+#define CG_PLAYER_STAMINA_1 93
+#define CG_PLAYER_STAMINA_2 94
+#define CG_PLAYER_STAMINA_3 95
+#define CG_PLAYER_STAMINA_4 96
+#define CG_PLAYER_STAMINA_BOLT 97
+#define CG_PLAYER_BOOST_BOLT 112
+#define CG_PLAYER_CLIPS_RING 98
+#define CG_PLAYER_BUILD_TIMER_RING 113
+#define CG_PLAYER_SELECT 74
+#define CG_PLAYER_SELECTTEXT 75
+#define CG_PLAYER_WEAPONICON 111
+#define CG_PLAYER_WALLCLIMBING 103
+#define CG_PLAYER_BOOSTED 104
+#define CG_PLAYER_POISON_BARBS 105
+#define CG_PLAYER_ALIEN_SENSE 108
+#define CG_PLAYER_HUMAN_SCANNER 109
+#define CG_PLAYER_USABLE_BUILDABLE 110
+#define CG_SELECTEDPLAYER_HEAD 7
+#define CG_SELECTEDPLAYER_NAME 8
+#define CG_SELECTEDPLAYER_LOCATION 9
+#define CG_SELECTEDPLAYER_STATUS 10
+#define CG_SELECTEDPLAYER_WEAPON 11
+#define CG_SELECTEDPLAYER_POWERUP 12
+
+#define CG_FLAGCARRIER_HEAD 13
+#define CG_FLAGCARRIER_NAME 14
+#define CG_FLAGCARRIER_LOCATION 15
+#define CG_FLAGCARRIER_STATUS 16
+#define CG_FLAGCARRIER_WEAPON 17
+#define CG_FLAGCARRIER_POWERUP 18
+
+#define CG_PLAYER_ITEM 19
+#define CG_PLAYER_SCORE 20
+
+#define CG_BLUE_FLAGHEAD 21
+#define CG_BLUE_FLAGSTATUS 22
+#define CG_BLUE_FLAGNAME 23
+#define CG_RED_FLAGHEAD 24
+#define CG_RED_FLAGSTATUS 25
+#define CG_RED_FLAGNAME 26
+
+#define CG_BLUE_SCORE 27
+#define CG_RED_SCORE 28
+#define CG_RED_NAME 29
+#define CG_BLUE_NAME 30
+#define CG_HARVESTER_SKULLS 31 // only shows in harvester
+#define CG_ONEFLAG_STATUS 32 // only shows in one flag
+#define CG_PLAYER_LOCATION 33
+#define CG_TEAM_COLOR 34
+#define CG_CTF_POWERUP 35
+
+#define CG_AREA_POWERUP 36
+#define CG_AREA_LAGOMETER 37 // painted with old system
+#define CG_PLAYER_HASFLAG 38
+#define CG_GAME_TYPE 39 // not done
+
+#define CG_SELECTEDPLAYER_ARMOR 40
+#define CG_SELECTEDPLAYER_HEALTH 41
+#define CG_PLAYER_STATUS 42
+#define CG_FRAGGED_MSG 43 // painted with old system
+#define CG_PROXMINED_MSG 44 // painted with old system
+#define CG_AREA_FPSINFO 45 // painted with old system
+#define CG_GAME_STATUS 49
+#define CG_KILLER 50
+#define CG_PLAYER_ARMOR_ICON2D 51
+#define CG_PLAYER_AMMO_ICON2D 52
+#define CG_ACCURACY 53
+#define CG_ASSISTS 54
+#define CG_DEFEND 55
+#define CG_EXCELLENT 56
+#define CG_IMPRESSIVE 57
+#define CG_PERFECT 58
+#define CG_GAUNTLET 59
+#define CG_SPECTATORS 60
+#define CG_TEAMINFO 61
+#define CG_VOICE_HEAD 62
+#define CG_VOICE_NAME 63
+#define CG_PLAYER_HASFLAG2D 64
+#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester
+#define CG_CAPFRAGLIMIT 66
+#define CG_1STPLACE 67
+#define CG_2NDPLACE 68
+#define CG_CAPTURES 69
+
+//TA: loading screen
+#define CG_LOAD_LEVELSHOT 76
+#define CG_LOAD_MEDIA 77
+#define CG_LOAD_MEDIA_LABEL 78
+#define CG_LOAD_BUILDABLES 79
+#define CG_LOAD_BUILDABLES_LABEL 80
+#define CG_LOAD_CHARMODEL 81
+#define CG_LOAD_CHARMODEL_LABEL 82
+#define CG_LOAD_OVERALL 83
+#define CG_LOAD_LEVELNAME 84
+#define CG_LOAD_MOTD 85
+#define CG_LOAD_HOSTNAME 86
+
+#define CG_FPS 87
+#define CG_FPS_FIXED 100
+#define CG_TIMER 88
+#define CG_TIMER_MINS 101
+#define CG_TIMER_SECS 102
+#define CG_SNAPSHOT 89
+#define CG_LAGOMETER 90
+#define CG_PLAYER_CROSSHAIRNAMES 114
+#define CG_STAGE_REPORT_TEXT 116
+#define CG_DEMO_PLAYBACK 117
+#define CG_DEMO_RECORDING 118
+
+#define CG_CONSOLE 91
+#define CG_TUTORIAL 119
+#define CG_CLOCK 120
+
+
+
+#define UI_OWNERDRAW_BASE 200
+#define UI_HANDICAP 200
+#define UI_PLAYERMODEL 202
+#define UI_CLANNAME 203
+#define UI_CLANLOGO 204
+#define UI_GAMETYPE 205
+#define UI_MAPPREVIEW 206
+#define UI_SKILL 207
+#define UI_BLUETEAMNAME 208
+#define UI_REDTEAMNAME 209
+#define UI_BLUETEAM1 210
+#define UI_BLUETEAM2 211
+#define UI_BLUETEAM3 212
+#define UI_BLUETEAM4 213
+#define UI_BLUETEAM5 214
+#define UI_REDTEAM1 215
+#define UI_REDTEAM2 216
+#define UI_REDTEAM3 217
+#define UI_REDTEAM4 218
+#define UI_REDTEAM5 219
+#define UI_NETSOURCE 220
+#define UI_NETMAPPREVIEW 221
+#define UI_NETFILTER 222
+#define UI_TIER 223
+#define UI_OPPONENTMODEL 224
+#define UI_TIERMAP1 225
+#define UI_TIERMAP2 226
+#define UI_TIERMAP3 227
+#define UI_PLAYERLOGO 228
+#define UI_OPPONENTLOGO 229
+#define UI_PLAYERLOGO_METAL 230
+#define UI_OPPONENTLOGO_METAL 231
+#define UI_PLAYERLOGO_NAME 232
+#define UI_OPPONENTLOGO_NAME 233
+#define UI_TIER_MAPNAME 234
+#define UI_TIER_GAMETYPE 235
+#define UI_ALLMAPS_SELECTION 236
+#define UI_OPPONENT_NAME 237
+#define UI_VOTE_KICK 238
+#define UI_BOTNAME 239
+#define UI_BOTSKILL 240
+#define UI_REDBLUE 241
+#define UI_SELECTEDPLAYER 243
+#define UI_MAPCINEMATIC 244
+#define UI_NETGAMETYPE 245
+#define UI_NETMAPCINEMATIC 246
+#define UI_SERVERREFRESHDATE 247
+#define UI_SERVERMOTD 248
+#define UI_GLINFO 249
+#define UI_KEYBINDSTATUS 250
+#define UI_CLANCINEMATIC 251
+#define UI_MAP_TIMETOBEAT 252
+#define UI_JOINGAMETYPE 253
+#define UI_PREVIEWCINEMATIC 254
+#define UI_STARTMAPCINEMATIC 255
+#define UI_MAPS_SELECTION 256
+
+//TA:
+//#define UI_DIALOG 257
+#define UI_TEAMINFOPANE 258
+#define UI_ACLASSINFOPANE 259
+#define UI_AUPGRADEINFOPANE 260
+#define UI_HITEMINFOPANE 261
+#define UI_HBUYINFOPANE 262
+#define UI_HSELLINFOPANE 263
+#define UI_ABUILDINFOPANE 264
+#define UI_HBUILDINFOPANE 265
+
+#define UI_PLAYERLIST_SELECTION 266
+#define UI_TEAMLIST_SELECTION 267
+
+#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag
+#define VOICECHAT_OFFENSE "offense" // command someone to go on offense
+#define VOICECHAT_DEFEND "defend" // command someone to go on defense
+#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag
+#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam)
+#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one)
+#define VOICECHAT_FOLLOWME "followme" // command someone to follow you
+#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag
+#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier
+#define VOICECHAT_YES "yes" // yes, affirmative, etc.
+#define VOICECHAT_NO "no" // no, negative, etc.
+#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag
+#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense
+#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense
+#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming)
+#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere
+#define VOICECHAT_ONFOLLOW "onfollow" // I'm following
+#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier
+#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag
+#define VOICECHAT_INPOSITION "inposition" // I'm in position
+#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag
+#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack
+#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF)
+#define VOICECHAT_STARTLEADER "startleader" // I'm the leader
+#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership
+#define VOICECHAT_TRASH "trash" // lots of trash talk
+#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader
+#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense
+#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense
diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c
new file mode 100644
index 0000000..a3033c4
--- /dev/null
+++ b/src/ui/ui_atoms.c
@@ -0,0 +1,556 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/**********************************************************************
+ UI_ATOMS.C
+
+ User interface building blocks and support functions.
+**********************************************************************/
+#include "ui_local.h"
+
+qboolean m_entersound; // after a frame, so caching won't disrupt the sound
+
+void QDECL Com_Error( int level, const char *error, ... ) {
+ va_list argptr;
+ char text[1024];
+
+ va_start (argptr, error);
+ vsprintf (text, error, argptr);
+ va_end (argptr);
+
+ trap_Error( va("%s", text) );
+}
+
+void QDECL Com_Printf( const char *msg, ... ) {
+ va_list argptr;
+ char text[1024];
+
+ va_start (argptr, msg);
+ vsprintf (text, msg, argptr);
+ va_end (argptr);
+
+ trap_Print( va("%s", text) );
+}
+
+qboolean newUI = qfalse;
+
+
+/*
+=================
+UI_ClampCvar
+=================
+*/
+float UI_ClampCvar( float min, float max, float value )
+{
+ if ( value < min ) return min;
+ if ( value > max ) return max;
+ return value;
+}
+
+/*
+=================
+UI_StartDemoLoop
+=================
+*/
+void UI_StartDemoLoop( void ) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" );
+}
+
+char *UI_Argv( int arg ) {
+ static char buffer[MAX_STRING_CHARS];
+
+ trap_Argv( arg, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+
+char *UI_Cvar_VariableString( const char *var_name ) {
+ static char buffer[MAX_STRING_CHARS];
+
+ trap_Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+
+
+void UI_SetBestScores(postGameInfo_t *newInfo, qboolean postGame) {
+ trap_Cvar_Set("ui_scoreAccuracy", va("%i%%", newInfo->accuracy));
+ trap_Cvar_Set("ui_scoreImpressives", va("%i", newInfo->impressives));
+ trap_Cvar_Set("ui_scoreExcellents", va("%i", newInfo->excellents));
+ trap_Cvar_Set("ui_scoreDefends", va("%i", newInfo->defends));
+ trap_Cvar_Set("ui_scoreAssists", va("%i", newInfo->assists));
+ trap_Cvar_Set("ui_scoreGauntlets", va("%i", newInfo->gauntlets));
+ trap_Cvar_Set("ui_scoreScore", va("%i", newInfo->score));
+ trap_Cvar_Set("ui_scorePerfect", va("%i", newInfo->perfects));
+ trap_Cvar_Set("ui_scoreTeam", va("%i to %i", newInfo->redScore, newInfo->blueScore));
+ trap_Cvar_Set("ui_scoreBase", va("%i", newInfo->baseScore));
+ trap_Cvar_Set("ui_scoreTimeBonus", va("%i", newInfo->timeBonus));
+ trap_Cvar_Set("ui_scoreSkillBonus", va("%i", newInfo->skillBonus));
+ trap_Cvar_Set("ui_scoreShutoutBonus", va("%i", newInfo->shutoutBonus));
+ trap_Cvar_Set("ui_scoreTime", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60));
+ trap_Cvar_Set("ui_scoreCaptures", va("%i", newInfo->captures));
+ if (postGame) {
+ trap_Cvar_Set("ui_scoreAccuracy2", va("%i%%", newInfo->accuracy));
+ trap_Cvar_Set("ui_scoreImpressives2", va("%i", newInfo->impressives));
+ trap_Cvar_Set("ui_scoreExcellents2", va("%i", newInfo->excellents));
+ trap_Cvar_Set("ui_scoreDefends2", va("%i", newInfo->defends));
+ trap_Cvar_Set("ui_scoreAssists2", va("%i", newInfo->assists));
+ trap_Cvar_Set("ui_scoreGauntlets2", va("%i", newInfo->gauntlets));
+ trap_Cvar_Set("ui_scoreScore2", va("%i", newInfo->score));
+ trap_Cvar_Set("ui_scorePerfect2", va("%i", newInfo->perfects));
+ trap_Cvar_Set("ui_scoreTeam2", va("%i to %i", newInfo->redScore, newInfo->blueScore));
+ trap_Cvar_Set("ui_scoreBase2", va("%i", newInfo->baseScore));
+ trap_Cvar_Set("ui_scoreTimeBonus2", va("%i", newInfo->timeBonus));
+ trap_Cvar_Set("ui_scoreSkillBonus2", va("%i", newInfo->skillBonus));
+ trap_Cvar_Set("ui_scoreShutoutBonus2", va("%i", newInfo->shutoutBonus));
+ trap_Cvar_Set("ui_scoreTime2", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60));
+ trap_Cvar_Set("ui_scoreCaptures2", va("%i", newInfo->captures));
+ }
+}
+
+void UI_LoadBestScores(const char *map, int game) {
+ char fileName[MAX_QPATH];
+ fileHandle_t f;
+ postGameInfo_t newInfo;
+ memset(&newInfo, 0, sizeof(postGameInfo_t));
+ Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game);
+ if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) {
+ int size = 0;
+ trap_FS_Read(&size, sizeof(int), f);
+ if (size == sizeof(postGameInfo_t)) {
+ trap_FS_Read(&newInfo, sizeof(postGameInfo_t), f);
+ }
+ trap_FS_FCloseFile(f);
+ }
+ UI_SetBestScores(&newInfo, qfalse);
+
+ Com_sprintf(fileName, MAX_QPATH, "demos/%s_%d.dm_%d", map, game, (int)trap_Cvar_VariableValue("protocol"));
+ uiInfo.demoAvailable = qfalse;
+ if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) {
+ uiInfo.demoAvailable = qtrue;
+ trap_FS_FCloseFile(f);
+ }
+}
+
+/*
+===============
+UI_ClearScores
+===============
+*/
+void UI_ClearScores( void ) {
+ char gameList[4096];
+ char *gameFile;
+ int i, len, count, size;
+ fileHandle_t f;
+ postGameInfo_t newInfo;
+
+ count = trap_FS_GetFileList( "games", "game", gameList, sizeof(gameList) );
+
+ size = sizeof(postGameInfo_t);
+ memset(&newInfo, 0, size);
+
+ if (count > 0) {
+ gameFile = gameList;
+ for ( i = 0; i < count; i++ ) {
+ len = strlen(gameFile);
+ if (trap_FS_FOpenFile(va("games/%s",gameFile), &f, FS_WRITE) >= 0) {
+ trap_FS_Write(&size, sizeof(int), f);
+ trap_FS_Write(&newInfo, size, f);
+ trap_FS_FCloseFile(f);
+ }
+ gameFile += len + 1;
+ }
+ }
+
+ UI_SetBestScores(&newInfo, qfalse);
+
+}
+
+
+
+static void UI_Cache_f( void ) {
+ Display_CacheAll();
+}
+
+/*
+=======================
+UI_CalcPostGameStats
+=======================
+*/
+static void UI_CalcPostGameStats( void ) {
+ char map[MAX_QPATH];
+ char fileName[MAX_QPATH];
+ char info[MAX_INFO_STRING];
+ fileHandle_t f;
+ int size, game, time, adjustedTime;
+ postGameInfo_t oldInfo;
+ postGameInfo_t newInfo;
+ qboolean newHigh = qfalse;
+
+ trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) );
+ Q_strncpyz( map, Info_ValueForKey( info, "mapname" ), sizeof(map) );
+ game = atoi(Info_ValueForKey(info, "g_gametype"));
+
+ // compose file name
+ Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game);
+ // see if we have one already
+ memset(&oldInfo, 0, sizeof(postGameInfo_t));
+ if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) {
+ // if so load it
+ size = 0;
+ trap_FS_Read(&size, sizeof(int), f);
+ if (size == sizeof(postGameInfo_t)) {
+ trap_FS_Read(&oldInfo, sizeof(postGameInfo_t), f);
+ }
+ trap_FS_FCloseFile(f);
+ }
+
+ newInfo.accuracy = atoi(UI_Argv(3));
+ newInfo.impressives = atoi(UI_Argv(4));
+ newInfo.excellents = atoi(UI_Argv(5));
+ newInfo.defends = atoi(UI_Argv(6));
+ newInfo.assists = atoi(UI_Argv(7));
+ newInfo.gauntlets = atoi(UI_Argv(8));
+ newInfo.baseScore = atoi(UI_Argv(9));
+ newInfo.perfects = atoi(UI_Argv(10));
+ newInfo.redScore = atoi(UI_Argv(11));
+ newInfo.blueScore = atoi(UI_Argv(12));
+ time = atoi(UI_Argv(13));
+ newInfo.captures = atoi(UI_Argv(14));
+
+ newInfo.time = (time - trap_Cvar_VariableValue("ui_matchStartTime")) / 1000;
+ adjustedTime = uiInfo.mapList[ui_currentMap.integer].timeToBeat[game];
+ if (newInfo.time < adjustedTime) {
+ newInfo.timeBonus = (adjustedTime - newInfo.time) * 10;
+ } else {
+ newInfo.timeBonus = 0;
+ }
+
+ if (newInfo.redScore > newInfo.blueScore && newInfo.blueScore <= 0) {
+ newInfo.shutoutBonus = 100;
+ } else {
+ newInfo.shutoutBonus = 0;
+ }
+
+ newInfo.skillBonus = trap_Cvar_VariableValue("g_spSkill");
+ if (newInfo.skillBonus <= 0) {
+ newInfo.skillBonus = 1;
+ }
+ newInfo.score = newInfo.baseScore + newInfo.shutoutBonus + newInfo.timeBonus;
+ newInfo.score *= newInfo.skillBonus;
+
+ // see if the score is higher for this one
+ newHigh = (newInfo.redScore > newInfo.blueScore && newInfo.score > oldInfo.score);
+
+ if (newHigh) {
+ // if so write out the new one
+ uiInfo.newHighScoreTime = uiInfo.uiDC.realTime + 20000;
+ if (trap_FS_FOpenFile(fileName, &f, FS_WRITE) >= 0) {
+ size = sizeof(postGameInfo_t);
+ trap_FS_Write(&size, sizeof(int), f);
+ trap_FS_Write(&newInfo, sizeof(postGameInfo_t), f);
+ trap_FS_FCloseFile(f);
+ }
+ }
+
+ if (newInfo.time < oldInfo.time) {
+ uiInfo.newBestTime = uiInfo.uiDC.realTime + 20000;
+ }
+
+ // put back all the ui overrides
+ trap_Cvar_Set("capturelimit", UI_Cvar_VariableString("ui_saveCaptureLimit"));
+ trap_Cvar_Set("fraglimit", UI_Cvar_VariableString("ui_saveFragLimit"));
+ trap_Cvar_Set("cg_drawTimer", UI_Cvar_VariableString("ui_drawTimer"));
+ trap_Cvar_Set("g_doWarmup", UI_Cvar_VariableString("ui_doWarmup"));
+ trap_Cvar_Set("g_Warmup", UI_Cvar_VariableString("ui_Warmup"));
+ trap_Cvar_Set("sv_pure", UI_Cvar_VariableString("ui_pure"));
+ trap_Cvar_Set("g_friendlyFire", UI_Cvar_VariableString("ui_friendlyFire"));
+
+ UI_SetBestScores(&newInfo, qtrue);
+ UI_ShowPostGame(newHigh);
+
+
+}
+
+static void UI_MessageMode_f( void )
+{
+ char *arg = UI_Argv( 0 );
+
+ trap_Cvar_Set( "ui_sayBuffer", "" );
+
+ switch( arg[ 11 ] )
+ {
+ default:
+ case '\0':
+ // Global
+ uiInfo.chatTeam = qfalse;
+ break;
+
+ case '2':
+ // Team
+ uiInfo.chatTeam = qtrue;
+ break;
+ }
+
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_CloseByName( "say" );
+ Menus_CloseByName( "say_team" );
+
+ if( uiInfo.chatTeam )
+ Menus_ActivateByName( "say_team" );
+ else
+ Menus_ActivateByName( "say" );
+}
+
+
+/*
+=================
+UI_ConsoleCommand
+=================
+*/
+qboolean UI_ConsoleCommand( int realTime )
+{
+ char *cmd;
+ char *arg1;
+
+ uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime;
+ uiInfo.uiDC.realTime = realTime;
+
+ cmd = UI_Argv( 0 );
+
+ // ensure minimum menu data is available
+ //Menu_Cache();
+
+ if ( Q_stricmp (cmd, "ui_test") == 0 ) {
+ UI_ShowPostGame(qtrue);
+ }
+
+ if ( Q_stricmp (cmd, "ui_report") == 0 ) {
+ UI_Report();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "ui_load") == 0 ) {
+ UI_Load();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "remapShader") == 0 ) {
+ if (trap_Argc() == 4) {
+ char shader1[MAX_QPATH];
+ char shader2[MAX_QPATH];
+ Q_strncpyz(shader1, UI_Argv(1), sizeof(shader1));
+ Q_strncpyz(shader2, UI_Argv(2), sizeof(shader2));
+ trap_R_RemapShader(shader1, shader2, UI_Argv(3));
+ return qtrue;
+ }
+ }
+
+ if ( Q_stricmp (cmd, "postgame") == 0 ) {
+ UI_CalcPostGameStats();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "ui_cache") == 0 ) {
+ UI_Cache_f();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "ui_teamOrders") == 0 ) {
+ //UI_TeamOrdersMenu_f();
+ return qtrue;
+ }
+
+ if( Q_stricmp ( cmd, "menu" ) == 0 )
+ {
+ arg1 = UI_Argv( 1 );
+
+ if( Menu_Count( ) > 0 )
+ {
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_ActivateByName( arg1 );
+ return qtrue;
+ }
+ }
+
+ if( Q_stricmp ( cmd, "closemenus" ) == 0 )
+ {
+ if( Menu_Count( ) > 0 )
+ {
+ trap_Key_SetCatcher( trap_Key_GetCatcher( ) & ~KEYCATCH_UI );
+ trap_Key_ClearStates( );
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll( );
+ return qtrue;
+ }
+ }
+
+ if( Q_stricmp ( cmd, "messagemode" ) == 0 ||
+ Q_stricmp ( cmd, "messagemode2" ) == 0 )
+ {
+ UI_MessageMode_f();
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+=================
+UI_Shutdown
+=================
+*/
+void UI_Shutdown( void ) {
+}
+
+/*
+================
+UI_AdjustFrom640
+
+Adjusted for resolution and screen aspect ratio
+================
+*/
+void UI_AdjustFrom640( float *x, float *y, float *w, float *h ) {
+ // expect valid pointers
+#if 0
+ *x = *x * uiInfo.uiDC.scale + uiInfo.uiDC.bias;
+ *y *= uiInfo.uiDC.scale;
+ *w *= uiInfo.uiDC.scale;
+ *h *= uiInfo.uiDC.scale;
+#endif
+
+ *x *= uiInfo.uiDC.xscale;
+ *y *= uiInfo.uiDC.yscale;
+ *w *= uiInfo.uiDC.xscale;
+ *h *= uiInfo.uiDC.yscale;
+
+}
+
+void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) {
+ qhandle_t hShader;
+
+ hShader = trap_R_RegisterShaderNoMip( picname );
+ UI_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+}
+
+void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ) {
+ float s0;
+ float s1;
+ float t0;
+ float t1;
+
+ if( w < 0 ) { // flip about vertical
+ w = -w;
+ s0 = 1;
+ s1 = 0;
+ }
+ else {
+ s0 = 0;
+ s1 = 1;
+ }
+
+ if( h < 0 ) { // flip about horizontal
+ h = -h;
+ t0 = 1;
+ t1 = 0;
+ }
+ else {
+ t0 = 0;
+ t1 = 1;
+ }
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader );
+}
+
+/*
+================
+UI_FillRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void UI_FillRect( float x, float y, float width, float height, const float *color ) {
+ trap_R_SetColor( color );
+
+ UI_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+
+ trap_R_SetColor( NULL );
+}
+
+void UI_DrawSides(float x, float y, float w, float h) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x + w - 1, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+
+void UI_DrawTopBottom(float x, float y, float w, float h) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x, y + h - 1, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+/*
+================
+UI_DrawRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void UI_DrawRect( float x, float y, float width, float height, const float *color ) {
+ trap_R_SetColor( color );
+
+ UI_DrawTopBottom(x, y, width, height);
+ UI_DrawSides(x, y, width, height);
+
+ trap_R_SetColor( NULL );
+}
+
+void UI_SetColor( const float *rgba ) {
+ trap_R_SetColor( rgba );
+}
+
+void UI_UpdateScreen( void ) {
+ trap_UpdateScreen();
+}
+
+
+void UI_DrawTextBox (int x, int y, int width, int lines)
+{
+ UI_FillRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorBlack );
+ UI_DrawRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorWhite );
+}
+
+qboolean UI_CursorInRect (int x, int y, int width, int height)
+{
+ if (uiInfo.uiDC.cursorx < x ||
+ uiInfo.uiDC.cursory < y ||
+ uiInfo.uiDC.cursorx > x+width ||
+ uiInfo.uiDC.cursory > y+height)
+ return qfalse;
+
+ return qtrue;
+}
diff --git a/src/ui/ui_gameinfo.c b/src/ui/ui_gameinfo.c
new file mode 100644
index 0000000..43639e5
--- /dev/null
+++ b/src/ui/ui_gameinfo.c
@@ -0,0 +1,333 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+//
+// gameinfo.c
+//
+
+#include "ui_local.h"
+
+
+//
+// arena and bot info
+//
+
+
+int ui_numBots;
+static char *ui_botInfos[MAX_BOTS];
+
+static int ui_numArenas;
+static char *ui_arenaInfos[MAX_ARENAS];
+
+/*
+===============
+UI_ParseInfos
+===============
+*/
+int UI_ParseInfos( char *buf, int max, char *infos[] ) {
+ char *token;
+ int count;
+ char key[MAX_TOKEN_CHARS];
+ char info[MAX_INFO_STRING];
+
+ count = 0;
+
+ while ( 1 ) {
+ token = COM_Parse( &buf );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( strcmp( token, "{" ) ) {
+ Com_Printf( "Missing { in info file\n" );
+ break;
+ }
+
+ if ( count == max ) {
+ Com_Printf( "Max infos exceeded\n" );
+ break;
+ }
+
+ info[0] = '\0';
+ while ( 1 ) {
+ token = COM_ParseExt( &buf, qtrue );
+ if ( !token[0] ) {
+ Com_Printf( "Unexpected end of info file\n" );
+ break;
+ }
+ if ( !strcmp( token, "}" ) ) {
+ break;
+ }
+ Q_strncpyz( key, token, sizeof( key ) );
+
+ token = COM_ParseExt( &buf, qfalse );
+ if ( !token[0] ) {
+ strcpy( token, "<NULL>" );
+ }
+ Info_SetValueForKey( info, key, token );
+ }
+ //NOTE: extra space for arena number
+ infos[count] = UI_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1);
+ if (infos[count]) {
+ strcpy(infos[count], info);
+ count++;
+ }
+ }
+ return count;
+}
+
+/*
+===============
+UI_LoadArenasFromFile
+===============
+*/
+static void UI_LoadArenasFromFile( char *filename ) {
+ int len;
+ fileHandle_t f;
+ char buf[MAX_ARENAS_TEXT];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( !f ) {
+ trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
+ return;
+ }
+ if ( len >= MAX_ARENAS_TEXT ) {
+ trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] );
+}
+
+/*
+=================
+UI_MapNameCompare
+=================
+*/
+static int UI_MapNameCompare( const void *a, const void *b )
+{
+ mapInfo *A = (mapInfo *)a;
+ mapInfo *B = (mapInfo *)b;
+
+ return Q_stricmp( A->mapName, B->mapName );
+}
+
+/*
+===============
+UI_LoadArenas
+===============
+*/
+void UI_LoadArenas( void ) {
+ int numdirs;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i, n;
+ int dirlen;
+ char *type;
+
+ ui_numArenas = 0;
+ uiInfo.mapCount = 0;
+
+ // get all arenas from .arena files
+ numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
+ dirptr = dirlist;
+ for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
+ dirlen = strlen(dirptr);
+ strcpy(filename, "scripts/");
+ strcat(filename, dirptr);
+ UI_LoadArenasFromFile(filename);
+ }
+ trap_Print( va( "[skipnotify]%i arenas parsed\n", ui_numArenas ) );
+ if (UI_OutOfMemory()) {
+ trap_Print(S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n");
+ }
+
+ for( n = 0; n < ui_numArenas; n++ )
+ {
+ // determine type
+ type = Info_ValueForKey( ui_arenaInfos[ n ], "type" );
+ // if no type specified, it will be treated as "ffa"
+
+ if( *type && strstr( type, "tremulous" ) )
+ uiInfo.mapList[ uiInfo.mapCount ].typeBits |= ( 1 << 0 );
+ else
+ continue; //not a trem map
+
+ uiInfo.mapList[uiInfo.mapCount].cinematic = -1;
+ uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "map"));
+ uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "longname"));
+ uiInfo.mapList[uiInfo.mapCount].levelShot = -1;
+ uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc(va("levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName));
+
+ uiInfo.mapCount++;
+ if( uiInfo.mapCount >= MAX_MAPS )
+ break;
+ }
+
+ qsort( uiInfo.mapList, uiInfo.mapCount, sizeof( mapInfo ), UI_MapNameCompare );
+}
+
+
+/*
+===============
+UI_LoadBotsFromFile
+===============
+*/
+static void UI_LoadBotsFromFile( char *filename ) {
+ int len;
+ fileHandle_t f;
+ char buf[MAX_BOTS_TEXT];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( !f ) {
+ trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
+ return;
+ }
+ if ( len >= MAX_BOTS_TEXT ) {
+ trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ COM_Compress(buf);
+
+ ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] );
+}
+
+/*
+===============
+UI_LoadBots
+===============
+*/
+void UI_LoadBots( void ) {
+ vmCvar_t botsFile;
+ int numdirs;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i;
+ int dirlen;
+
+ ui_numBots = 0;
+
+ trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
+ if( *botsFile.string ) {
+ UI_LoadBotsFromFile(botsFile.string);
+ }
+ else {
+ UI_LoadBotsFromFile("scripts/bots.txt");
+ }
+
+ // get all bots from .bot files
+ numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
+ dirptr = dirlist;
+ for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
+ dirlen = strlen(dirptr);
+ strcpy(filename, "scripts/");
+ strcat(filename, dirptr);
+ UI_LoadBotsFromFile(filename);
+ }
+ trap_Print( va( "%i bots parsed\n", ui_numBots ) );
+}
+
+
+/*
+===============
+UI_GetBotInfoByNumber
+===============
+*/
+char *UI_GetBotInfoByNumber( int num ) {
+ if( num < 0 || num >= ui_numBots ) {
+ trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
+ return NULL;
+ }
+ return ui_botInfos[num];
+}
+
+
+/*
+===============
+UI_GetBotInfoByName
+===============
+*/
+char *UI_GetBotInfoByName( const char *name ) {
+ int n;
+ char *value;
+
+ for ( n = 0; n < ui_numBots ; n++ ) {
+ value = Info_ValueForKey( ui_botInfos[n], "name" );
+ if ( !Q_stricmp( value, name ) ) {
+ return ui_botInfos[n];
+ }
+ }
+
+ return NULL;
+}
+
+int UI_GetNumBots() {
+ return ui_numBots;
+}
+
+
+char *UI_GetBotNameByNumber( int num ) {
+ char *info = UI_GetBotInfoByNumber(num);
+ if (info) {
+ return Info_ValueForKey( info, "name" );
+ }
+ return "Sarge";
+}
+
+void UI_ServerInfo( void )
+{
+ char info[ MAX_INFO_VALUE ];
+
+ info[0] = '\0';
+ if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) )
+ {
+ trap_Cvar_Set( "ui_serverinfo_mapname",
+ Info_ValueForKey( info, "mapname" ) );
+ trap_Cvar_Set( "ui_serverinfo_timelimit",
+ Info_ValueForKey( info, "timelimit" ) );
+ trap_Cvar_Set( "ui_serverinfo_sd",
+ Info_ValueForKey( info, "g_suddenDeathTime" ) );
+ trap_Cvar_Set( "ui_serverinfo_hostname",
+ Info_ValueForKey( info, "sv_hostname" ) );
+ trap_Cvar_Set( "ui_serverinfo_maxclients",
+ Info_ValueForKey( info, "sv_maxclients" ) );
+ trap_Cvar_Set( "ui_serverinfo_version",
+ Info_ValueForKey( info, "version" ) );
+ trap_Cvar_Set( "ui_serverinfo_unlagged",
+ Info_ValueForKey( info, "g_unlagged" ) );
+ trap_Cvar_Set( "ui_serverinfo_ff",
+ Info_ValueForKey( info, "ff" ) );
+ }
+}
diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h
new file mode 100644
index 0000000..0066593
--- /dev/null
+++ b/src/ui/ui_local.h
@@ -0,0 +1,1210 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef __UI_LOCAL_H__
+#define __UI_LOCAL_H__
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "ui_public.h"
+#include "../client/keycodes.h"
+#include "../game/bg_public.h"
+#include "ui_shared.h"
+
+// global display context
+
+extern vmCvar_t ui_ffa_fraglimit;
+extern vmCvar_t ui_ffa_timelimit;
+
+extern vmCvar_t ui_tourney_fraglimit;
+extern vmCvar_t ui_tourney_timelimit;
+
+extern vmCvar_t ui_team_fraglimit;
+extern vmCvar_t ui_team_timelimit;
+extern vmCvar_t ui_team_friendly;
+
+extern vmCvar_t ui_ctf_capturelimit;
+extern vmCvar_t ui_ctf_timelimit;
+extern vmCvar_t ui_ctf_friendly;
+
+extern vmCvar_t ui_arenasFile;
+extern vmCvar_t ui_botsFile;
+extern vmCvar_t ui_spScores1;
+extern vmCvar_t ui_spScores2;
+extern vmCvar_t ui_spScores3;
+extern vmCvar_t ui_spScores4;
+extern vmCvar_t ui_spScores5;
+extern vmCvar_t ui_spAwards;
+extern vmCvar_t ui_spVideos;
+extern vmCvar_t ui_spSkill;
+
+extern vmCvar_t ui_spSelection;
+
+extern vmCvar_t ui_browserMaster;
+extern vmCvar_t ui_browserGameType;
+extern vmCvar_t ui_browserSortKey;
+extern vmCvar_t ui_browserShowFull;
+extern vmCvar_t ui_browserShowEmpty;
+
+extern vmCvar_t ui_brassTime;
+extern vmCvar_t ui_drawCrosshair;
+extern vmCvar_t ui_drawCrosshairNames;
+extern vmCvar_t ui_marks;
+
+extern vmCvar_t ui_server1;
+extern vmCvar_t ui_server2;
+extern vmCvar_t ui_server3;
+extern vmCvar_t ui_server4;
+extern vmCvar_t ui_server5;
+extern vmCvar_t ui_server6;
+extern vmCvar_t ui_server7;
+extern vmCvar_t ui_server8;
+extern vmCvar_t ui_server9;
+extern vmCvar_t ui_server10;
+extern vmCvar_t ui_server11;
+extern vmCvar_t ui_server12;
+extern vmCvar_t ui_server13;
+extern vmCvar_t ui_server14;
+extern vmCvar_t ui_server15;
+extern vmCvar_t ui_server16;
+
+extern vmCvar_t ui_captureLimit;
+extern vmCvar_t ui_fragLimit;
+extern vmCvar_t ui_gameType;
+extern vmCvar_t ui_netGameType;
+extern vmCvar_t ui_actualNetGameType;
+extern vmCvar_t ui_joinGameType;
+extern vmCvar_t ui_netSource;
+extern vmCvar_t ui_serverFilterType;
+extern vmCvar_t ui_dedicated;
+extern vmCvar_t ui_opponentName;
+extern vmCvar_t ui_menuFiles;
+extern vmCvar_t ui_currentTier;
+extern vmCvar_t ui_currentMap;
+extern vmCvar_t ui_currentNetMap;
+extern vmCvar_t ui_mapIndex;
+extern vmCvar_t ui_currentOpponent;
+extern vmCvar_t ui_selectedPlayer;
+extern vmCvar_t ui_selectedPlayerName;
+extern vmCvar_t ui_lastServerRefresh_0;
+extern vmCvar_t ui_lastServerRefresh_1;
+extern vmCvar_t ui_lastServerRefresh_2;
+extern vmCvar_t ui_lastServerRefresh_3;
+extern vmCvar_t ui_singlePlayerActive;
+extern vmCvar_t ui_scoreAccuracy;
+extern vmCvar_t ui_scoreImpressives;
+extern vmCvar_t ui_scoreExcellents;
+extern vmCvar_t ui_scoreDefends;
+extern vmCvar_t ui_scoreAssists;
+extern vmCvar_t ui_scoreGauntlets;
+extern vmCvar_t ui_scoreScore;
+extern vmCvar_t ui_scorePerfect;
+extern vmCvar_t ui_scoreTeam;
+extern vmCvar_t ui_scoreBase;
+extern vmCvar_t ui_scoreTimeBonus;
+extern vmCvar_t ui_scoreSkillBonus;
+extern vmCvar_t ui_scoreShutoutBonus;
+extern vmCvar_t ui_scoreTime;
+extern vmCvar_t ui_smallFont;
+extern vmCvar_t ui_bigFont;
+extern vmCvar_t ui_serverStatusTimeOut;
+
+//TA: bank values
+extern vmCvar_t ui_bank;
+
+extern vmCvar_t ui_chatCommands;
+
+//
+// ui_qmenu.c
+//
+
+#define RCOLUMN_OFFSET ( BIGCHAR_WIDTH )
+#define LCOLUMN_OFFSET (-BIGCHAR_WIDTH )
+
+#define SLIDER_RANGE 10
+#define MAX_EDIT_LINE 256
+
+#define MAX_MENUDEPTH 8
+#define MAX_MENUITEMS 128
+
+#define MTYPE_NULL 0
+#define MTYPE_SLIDER 1
+#define MTYPE_ACTION 2
+#define MTYPE_SPINCONTROL 3
+#define MTYPE_FIELD 4
+#define MTYPE_RADIOBUTTON 5
+#define MTYPE_BITMAP 6
+#define MTYPE_TEXT 7
+#define MTYPE_SCROLLLIST 8
+#define MTYPE_PTEXT 9
+#define MTYPE_BTEXT 10
+
+#define QMF_BLINK 0x00000001
+#define QMF_SMALLFONT 0x00000002
+#define QMF_LEFT_JUSTIFY 0x00000004
+#define QMF_CENTER_JUSTIFY 0x00000008
+#define QMF_RIGHT_JUSTIFY 0x00000010
+#define QMF_NUMBERSONLY 0x00000020 // edit field is only numbers
+#define QMF_HIGHLIGHT 0x00000040
+#define QMF_HIGHLIGHT_IF_FOCUS 0x00000080 // steady focus
+#define QMF_PULSEIFFOCUS 0x00000100 // pulse if focus
+#define QMF_HASMOUSEFOCUS 0x00000200
+#define QMF_NOONOFFTEXT 0x00000400
+#define QMF_MOUSEONLY 0x00000800 // only mouse input allowed
+#define QMF_HIDDEN 0x00001000 // skips drawing
+#define QMF_GRAYED 0x00002000 // grays and disables
+#define QMF_INACTIVE 0x00004000 // disables any input
+#define QMF_NODEFAULTINIT 0x00008000 // skip default initialization
+#define QMF_OWNERDRAW 0x00010000
+#define QMF_PULSE 0x00020000
+#define QMF_LOWERCASE 0x00040000 // edit field is all lower case
+#define QMF_UPPERCASE 0x00080000 // edit field is all upper case
+#define QMF_SILENT 0x00100000
+
+// callback notifications
+#define QM_GOTFOCUS 1
+#define QM_LOSTFOCUS 2
+#define QM_ACTIVATED 3
+
+typedef struct _tag_menuframework
+{
+ int cursor;
+ int cursor_prev;
+
+ int nitems;
+ void *items[MAX_MENUITEMS];
+
+ void (*draw) (void);
+ sfxHandle_t (*key) (int key);
+
+ qboolean wrapAround;
+ qboolean fullscreen;
+ qboolean showlogo;
+} menuframework_s;
+
+typedef struct
+{
+ int type;
+ const char *name;
+ int id;
+ int x, y;
+ int left;
+ int top;
+ int right;
+ int bottom;
+ menuframework_s *parent;
+ int menuPosition;
+ unsigned flags;
+
+ void (*callback)( void *self, int event );
+ void (*statusbar)( void *self );
+ void (*ownerdraw)( void *self );
+} menucommon_s;
+
+typedef struct {
+ int cursor;
+ int scroll;
+ int widthInChars;
+ char buffer[MAX_EDIT_LINE];
+ int maxchars;
+} mfield_t;
+
+typedef struct
+{
+ menucommon_s generic;
+ mfield_t field;
+} menufield_s;
+
+typedef struct
+{
+ menucommon_s generic;
+
+ float minvalue;
+ float maxvalue;
+ float curvalue;
+
+ float range;
+} menuslider_s;
+
+typedef struct
+{
+ menucommon_s generic;
+
+ int oldvalue;
+ int curvalue;
+ int numitems;
+ int top;
+
+ const char **itemnames;
+
+ int width;
+ int height;
+ int columns;
+ int seperation;
+} menulist_s;
+
+typedef struct
+{
+ menucommon_s generic;
+} menuaction_s;
+
+typedef struct
+{
+ menucommon_s generic;
+ int curvalue;
+} menuradiobutton_s;
+
+typedef struct
+{
+ menucommon_s generic;
+ char* focuspic;
+ char* errorpic;
+ qhandle_t shader;
+ qhandle_t focusshader;
+ int width;
+ int height;
+ float* focuscolor;
+} menubitmap_s;
+
+typedef struct
+{
+ menucommon_s generic;
+ char* string;
+ int style;
+ float* color;
+} menutext_s;
+
+extern void Menu_Cache( void );
+extern void Menu_Focus( menucommon_s *m );
+extern void Menu_AddItem( menuframework_s *menu, void *item );
+extern void Menu_AdjustCursor( menuframework_s *menu, int dir );
+extern void Menu_Draw( menuframework_s *menu );
+extern void *Menu_ItemAtCursor( menuframework_s *m );
+extern sfxHandle_t Menu_ActivateItem( menuframework_s *s, menucommon_s* item );
+extern void Menu_SetCursor( menuframework_s *s, int cursor );
+extern void Menu_SetCursorToItem( menuframework_s *m, void* ptr );
+extern sfxHandle_t Menu_DefaultKey( menuframework_s *s, int key );
+extern void Bitmap_Init( menubitmap_s *b );
+extern void Bitmap_Draw( menubitmap_s *b );
+extern void ScrollList_Draw( menulist_s *l );
+extern sfxHandle_t ScrollList_Key( menulist_s *l, int key );
+extern sfxHandle_t menu_in_sound;
+extern sfxHandle_t menu_move_sound;
+extern sfxHandle_t menu_out_sound;
+extern sfxHandle_t menu_buzz_sound;
+extern sfxHandle_t menu_null_sound;
+extern sfxHandle_t weaponChangeSound;
+extern vec4_t menu_text_color;
+extern vec4_t menu_grayed_color;
+extern vec4_t menu_dark_color;
+extern vec4_t menu_highlight_color;
+extern vec4_t menu_red_color;
+extern vec4_t menu_black_color;
+extern vec4_t menu_dim_color;
+extern vec4_t color_black;
+extern vec4_t color_white;
+extern vec4_t color_yellow;
+extern vec4_t color_blue;
+extern vec4_t color_orange;
+extern vec4_t color_red;
+extern vec4_t color_dim;
+extern vec4_t name_color;
+extern vec4_t list_color;
+extern vec4_t listbar_color;
+extern vec4_t text_color_disabled;
+extern vec4_t text_color_normal;
+extern vec4_t text_color_highlight;
+
+extern char *ui_medalNames[];
+extern char *ui_medalPicNames[];
+extern char *ui_medalSounds[];
+
+//
+// ui_mfield.c
+//
+extern void MField_Clear( mfield_t *edit );
+extern void MField_KeyDownEvent( mfield_t *edit, int key );
+extern void MField_CharEvent( mfield_t *edit, int ch );
+extern void MField_Draw( mfield_t *edit, int x, int y, int style, vec4_t color );
+extern void MenuField_Init( menufield_s* m );
+extern void MenuField_Draw( menufield_s *f );
+extern sfxHandle_t MenuField_Key( menufield_s* m, int* key );
+
+//
+// ui_main.c
+//
+void UI_Report( void );
+void UI_Load( void );
+void UI_LoadMenus(const char *menuFile, qboolean reset);
+void _UI_SetActiveMenu( uiMenuCommand_t menu );
+int UI_AdjustTimeByGame(int time);
+void UI_ShowPostGame(qboolean newHigh);
+void UI_ClearScores( void );
+void UI_LoadArenas(void);
+void UI_ServerInfo(void);
+
+//
+// ui_menu.c
+//
+extern void MainMenu_Cache( void );
+extern void UI_MainMenu(void);
+extern void UI_RegisterCvars( void );
+extern void UI_UpdateCvars( void );
+
+//
+// ui_credits.c
+//
+extern void UI_CreditMenu( void );
+
+//
+// ui_ingame.c
+//
+extern void InGame_Cache( void );
+extern void UI_InGameMenu(void);
+
+//
+// ui_confirm.c
+//
+extern void ConfirmMenu_Cache( void );
+extern void UI_ConfirmMenu( const char *question, void (*draw)( void ), void (*action)( qboolean result ) );
+
+//
+// ui_setup.c
+//
+extern void UI_SetupMenu_Cache( void );
+extern void UI_SetupMenu(void);
+
+//
+// ui_team.c
+//
+extern void UI_TeamMainMenu( void );
+extern void TeamMain_Cache( void );
+
+//
+// ui_connect.c
+//
+extern void UI_DrawConnectScreen( qboolean overlay );
+
+//
+// ui_controls2.c
+//
+extern void UI_ControlsMenu( void );
+extern void Controls_Cache( void );
+
+//
+// ui_demo2.c
+//
+extern void UI_DemosMenu( void );
+extern void Demos_Cache( void );
+
+//
+// ui_cinematics.c
+//
+extern void UI_CinematicsMenu( void );
+extern void UI_CinematicsMenu_f( void );
+extern void UI_CinematicsMenu_Cache( void );
+
+//
+// ui_mods.c
+//
+extern void UI_ModsMenu( void );
+extern void UI_ModsMenu_Cache( void );
+
+//
+// ui_playermodel.c
+//
+extern void UI_PlayerModelMenu( void );
+extern void PlayerModel_Cache( void );
+
+//
+// ui_playersettings.c
+//
+extern void UI_PlayerSettingsMenu( void );
+extern void PlayerSettings_Cache( void );
+
+//
+// ui_preferences.c
+//
+extern void UI_PreferencesMenu( void );
+extern void Preferences_Cache( void );
+
+//
+// ui_specifyleague.c
+//
+extern void UI_SpecifyLeagueMenu( void );
+extern void SpecifyLeague_Cache( void );
+
+//
+// ui_specifyserver.c
+//
+extern void UI_SpecifyServerMenu( void );
+extern void SpecifyServer_Cache( void );
+
+//
+// ui_servers2.c
+//
+#define MAX_FAVORITESERVERS 16
+
+extern void UI_ArenaServersMenu( void );
+extern void ArenaServers_Cache( void );
+
+//
+// ui_startserver.c
+//
+extern void UI_StartServerMenu( qboolean multiplayer );
+extern void StartServer_Cache( void );
+extern void ServerOptions_Cache( void );
+extern void UI_BotSelectMenu( char *bot );
+extern void UI_BotSelectMenu_Cache( void );
+
+//
+// ui_serverinfo.c
+//
+extern void UI_ServerInfoMenu( void );
+extern void ServerInfo_Cache( void );
+
+//
+// ui_video.c
+//
+extern void UI_GraphicsOptionsMenu( void );
+extern void GraphicsOptions_Cache( void );
+extern void DriverInfo_Cache( void );
+
+//
+// ui_players.c
+//
+
+//FIXME ripped from cg_local.h
+typedef struct {
+ int oldFrame;
+ int oldFrameTime; // time when ->oldFrame was exactly on
+
+ int frame;
+ int frameTime; // time when ->frame will be exactly on
+
+ float backlerp;
+
+ float yawAngle;
+ qboolean yawing;
+ float pitchAngle;
+ qboolean pitching;
+
+ int animationNumber; // may include ANIM_TOGGLEBIT
+ animation_t *animation;
+ int animationTime; // time when the first frame of the animation will be exact
+} lerpFrame_t;
+
+typedef struct {
+ // model info
+ qhandle_t legsModel;
+ qhandle_t legsSkin;
+ lerpFrame_t legs;
+
+ qhandle_t torsoModel;
+ qhandle_t torsoSkin;
+ lerpFrame_t torso;
+
+ qhandle_t headModel;
+ qhandle_t headSkin;
+
+ animation_t animations[MAX_PLAYER_TOTALANIMATIONS];
+
+ qhandle_t weaponModel;
+ qhandle_t barrelModel;
+ qhandle_t flashModel;
+ vec3_t flashDlightColor;
+ int muzzleFlashTime;
+
+ // currently in use drawing parms
+ vec3_t viewAngles;
+ vec3_t moveAngles;
+ weapon_t currentWeapon;
+ int legsAnim;
+ int torsoAnim;
+
+ // animation vars
+ weapon_t weapon;
+ weapon_t lastWeapon;
+ weapon_t pendingWeapon;
+ int weaponTimer;
+ int pendingLegsAnim;
+ int torsoAnimationTimer;
+
+ int pendingTorsoAnim;
+ int legsAnimationTimer;
+
+ qboolean chat;
+ qboolean newModel;
+
+ qboolean barrelSpinning;
+ float barrelAngle;
+ int barrelTime;
+
+ int realWeapon;
+} playerInfo_t;
+
+void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time );
+void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName );
+void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNum, qboolean chat );
+qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName , const char *headName, const char *teamName);
+
+//
+// ui_atoms.c
+//
+// this is only used in the old ui, the new ui has it's own version
+typedef struct {
+ int frametime;
+ int realtime;
+ int cursorx;
+ int cursory;
+ glconfig_t glconfig;
+ qboolean debug;
+ qhandle_t whiteShader;
+ qhandle_t charset;
+ qhandle_t charsetProp;
+ qhandle_t charsetPropGlow;
+ qhandle_t charsetPropB;
+ qhandle_t cursor;
+ qhandle_t rb_on;
+ qhandle_t rb_off;
+ float scale;
+ float bias;
+ qboolean demoversion;
+ qboolean firstdraw;
+} uiStatic_t;
+
+
+// new ui stuff
+#define UI_NUMFX 7
+#define MAX_HEADS 64
+#define MAX_ALIASES 64
+#define MAX_HEADNAME 32
+#define MAX_TEAMS 64
+#define MAX_GAMETYPES 16
+#define MAX_MAPS 128
+#define MAX_SPMAPS 16
+#define PLAYERS_PER_TEAM 5
+#define MAX_PINGREQUESTS 32
+#define MAX_ADDRESSLENGTH 64
+#define MAX_HOSTNAMELENGTH 22
+#define MAX_MAPNAMELENGTH 16
+#define MAX_STATUSLENGTH 64
+#define MAX_LISTBOXWIDTH 59
+#define UI_FONT_THRESHOLD 0.1
+#define MAX_DISPLAY_SERVERS 2048
+#define MAX_SERVERSTATUS_LINES 128
+#define MAX_SERVERSTATUS_TEXT 1024
+#define MAX_FOUNDPLAYER_SERVERS 16
+#define TEAM_MEMBERS 5
+#define GAMES_ALL 0
+#define GAMES_FFA 1
+#define GAMES_TEAMPLAY 2
+#define GAMES_TOURNEY 3
+#define GAMES_CTF 4
+#define MAPS_PER_TIER 3
+#define MAX_TIERS 16
+#define MAX_MODS 64
+#define MAX_DEMOS 256
+#define MAX_MOVIES 256
+#define MAX_PLAYERMODELS 256
+
+
+typedef struct {
+ const char *name;
+ const char *imageName;
+ qhandle_t headImage;
+ const char *base;
+ qboolean active;
+ int reference;
+} characterInfo;
+
+typedef struct {
+ const char *name;
+ const char *ai;
+ const char *action;
+} aliasInfo;
+
+typedef struct {
+ const char *teamName;
+ const char *imageName;
+ const char *teamMembers[TEAM_MEMBERS];
+ qhandle_t teamIcon;
+ qhandle_t teamIcon_Metal;
+ qhandle_t teamIcon_Name;
+ int cinematic;
+} teamInfo;
+
+typedef struct {
+ const char *gameType;
+ int gtEnum;
+} gameTypeInfo;
+
+typedef struct {
+ const char *mapName;
+ const char *mapLoadName;
+ const char *imageName;
+ const char *opponentName;
+ int teamMembers;
+ int typeBits;
+ int cinematic;
+ int timeToBeat[MAX_GAMETYPES];
+ qhandle_t levelShot;
+ qboolean active;
+} mapInfo;
+
+typedef struct {
+ const char *tierName;
+ const char *maps[MAPS_PER_TIER];
+ int gameTypes[MAPS_PER_TIER];
+ qhandle_t mapHandles[MAPS_PER_TIER];
+} tierInfo;
+
+typedef struct serverFilter_s {
+ const char *description;
+ const char *basedir;
+} serverFilter_t;
+
+typedef struct {
+ char adrstr[MAX_ADDRESSLENGTH];
+ int start;
+} pinglist_t;
+
+
+typedef struct serverStatus_s {
+ pinglist_t pingList[MAX_PINGREQUESTS];
+ int numqueriedservers;
+ int currentping;
+ int nextpingtime;
+ int maxservers;
+ int refreshtime;
+ int numServers;
+ int sortKey;
+ int sortDir;
+ qboolean sorted;
+ int lastCount;
+ qboolean refreshActive;
+ int currentServer;
+ int displayServers[MAX_DISPLAY_SERVERS];
+ int numDisplayServers;
+ int numPlayersOnServers;
+ int nextDisplayRefresh;
+ int nextSortTime;
+ qhandle_t currentServerPreview;
+ int currentServerCinematic;
+ int motdLen;
+ int motdWidth;
+ int motdPaintX;
+ int motdPaintX2;
+ int motdOffset;
+ int motdTime;
+ char motd[MAX_STRING_CHARS];
+} serverStatus_t;
+
+
+typedef struct {
+ char adrstr[MAX_ADDRESSLENGTH];
+ char name[MAX_ADDRESSLENGTH];
+ int startTime;
+ int serverNum;
+ qboolean valid;
+} pendingServer_t;
+
+typedef struct {
+ int num;
+ pendingServer_t server[MAX_SERVERSTATUSREQUESTS];
+} pendingServerStatus_t;
+
+typedef struct {
+ char address[MAX_ADDRESSLENGTH];
+ char *lines[MAX_SERVERSTATUS_LINES][4];
+ char text[MAX_SERVERSTATUS_TEXT];
+ char pings[MAX_CLIENTS * 3];
+ int numLines;
+} serverStatusInfo_t;
+
+typedef struct {
+ const char *modName;
+ const char *modDescr;
+} modInfo_t;
+
+//TA: tremulous menus
+#define MAX_INFOPANE_TEXT 4096
+#define MAX_INFOPANE_GRAPHICS 16
+#define MAX_INFOPANES 128
+
+typedef enum
+{
+ INFOPANE_TOP,
+ INFOPANE_BOTTOM,
+ INFOPANE_LEFT,
+ INFOPANE_RIGHT
+} tremIPSide_t;
+
+typedef struct
+{
+ qhandle_t graphic;
+
+ tremIPSide_t side;
+ int offset;
+
+ int width, height;
+} tremIPGraphic_t;
+
+typedef struct
+{
+ const char *name;
+ char text[ MAX_INFOPANE_TEXT ];
+ int align;
+
+ tremIPGraphic_t graphics[ MAX_INFOPANE_GRAPHICS ];
+ int numGraphics;
+} tremInfoPane_t;
+
+typedef struct
+{
+ const char *text;
+ const char *cmd;
+ tremInfoPane_t *infopane;
+} tremMenuItem_t;
+//TA: tremulous menus
+
+typedef struct {
+ displayContextDef_t uiDC;
+ int newHighScoreTime;
+ int newBestTime;
+ int showPostGameTime;
+ qboolean newHighScore;
+ qboolean demoAvailable;
+ qboolean soundHighScore;
+
+ int characterCount;
+ int botIndex;
+ characterInfo characterList[MAX_HEADS];
+
+ int aliasCount;
+ aliasInfo aliasList[MAX_ALIASES];
+
+ int teamCount;
+ teamInfo teamList[MAX_TEAMS];
+
+ int numGameTypes;
+ gameTypeInfo gameTypes[MAX_GAMETYPES];
+
+ int numJoinGameTypes;
+ gameTypeInfo joinGameTypes[MAX_GAMETYPES];
+
+ int redBlue;
+ int playerCount;
+ int myTeamCount;
+ int teamIndex;
+ int playerRefresh;
+ int playerIndex;
+ int playerNumber;
+ int myPlayerIndex;
+ int ignoreIndex;
+ qboolean teamLeader;
+ char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ int clientNums[MAX_CLIENTS];
+ int teamClientNums[MAX_CLIENTS];
+ clientList_t ignoreList[MAX_CLIENTS];
+
+ int mapCount;
+ mapInfo mapList[MAX_MAPS];
+
+
+ int tierCount;
+ tierInfo tierList[MAX_TIERS];
+
+ int skillIndex;
+
+ modInfo_t modList[MAX_MODS];
+ int modCount;
+ int modIndex;
+
+ const char *demoList[MAX_DEMOS];
+ int demoCount;
+ int demoIndex;
+
+ const char *movieList[MAX_MOVIES];
+ int movieCount;
+ int movieIndex;
+ int previewMovie;
+
+ tremInfoPane_t tremInfoPanes[ MAX_INFOPANES ];
+ int tremInfoPaneCount;
+
+//TA: tremulous menus
+ tremMenuItem_t tremTeamList[ 4 ];
+ int tremTeamCount;
+ int tremTeamIndex;
+
+ tremMenuItem_t tremAlienClassList[ 3 ];
+ int tremAlienClassCount;
+ int tremAlienClassIndex;
+
+ tremMenuItem_t tremHumanItemList[ 3 ];
+ int tremHumanItemCount;
+ int tremHumanItemIndex;
+
+ tremMenuItem_t tremHumanArmouryBuyList[ 32 ];
+ int tremHumanArmouryBuyCount;
+ int tremHumanArmouryBuyIndex;
+
+ tremMenuItem_t tremHumanArmourySellList[ 32 ];
+ int tremHumanArmourySellCount;
+ int tremHumanArmourySellIndex;
+
+ tremMenuItem_t tremAlienUpgradeList[ 16 ];
+ int tremAlienUpgradeCount;
+ int tremAlienUpgradeIndex;
+
+ tremMenuItem_t tremAlienBuildList[ 32 ];
+ int tremAlienBuildCount;
+ int tremAlienBuildIndex;
+
+ tremMenuItem_t tremHumanBuildList[ 32 ];
+ int tremHumanBuildCount;
+ int tremHumanBuildIndex;
+//TA: tremulous menus
+
+ serverStatus_t serverStatus;
+
+ // for the showing the status of a server
+ char serverStatusAddress[MAX_ADDRESSLENGTH];
+ serverStatusInfo_t serverStatusInfo;
+ int nextServerStatusRefresh;
+
+ // to retrieve the status of server to find a player
+ pendingServerStatus_t pendingServerStatus;
+ char findPlayerName[MAX_STRING_CHARS];
+ char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH];
+ char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH];
+ int currentFoundPlayerServer;
+ int numFoundPlayerServers;
+ int nextFindPlayerRefresh;
+
+ int currentCrosshair;
+ int startPostGameTime;
+ sfxHandle_t newHighScoreSound;
+
+ int q3HeadCount;
+ char q3HeadNames[MAX_PLAYERMODELS][64];
+ qhandle_t q3HeadIcons[MAX_PLAYERMODELS];
+ int q3SelectedHead;
+
+ int effectsColor;
+
+ qboolean inGameLoad;
+
+ qboolean chatTeam;
+} uiInfo_t;
+
+extern uiInfo_t uiInfo;
+
+
+extern void UI_Init( void );
+extern void UI_Shutdown( void );
+extern void UI_KeyEvent( int key );
+extern void UI_MouseEvent( int dx, int dy );
+extern void UI_Refresh( int realtime );
+extern qboolean UI_ConsoleCommand( int realTime );
+extern float UI_ClampCvar( float min, float max, float value );
+extern void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname );
+extern void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader );
+extern void UI_FillRect( float x, float y, float width, float height, const float *color );
+extern void UI_DrawRect( float x, float y, float width, float height, const float *color );
+extern void UI_DrawTopBottom(float x, float y, float w, float h);
+extern void UI_DrawSides(float x, float y, float w, float h);
+extern void UI_UpdateScreen( void );
+extern void UI_SetColor( const float *rgba );
+extern void UI_LerpColor(vec4_t a, vec4_t b, vec4_t c, float t);
+extern void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color );
+extern float UI_ProportionalSizeScale( int style );
+extern void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color );
+extern int UI_ProportionalStringWidth( const char* str );
+extern void UI_DrawString( int x, int y, const char* str, int style, vec4_t color );
+extern void UI_DrawChar( int x, int y, int ch, int style, vec4_t color );
+extern qboolean UI_CursorInRect (int x, int y, int width, int height);
+extern void UI_AdjustFrom640( float *x, float *y, float *w, float *h );
+extern void UI_DrawTextBox (int x, int y, int width, int lines);
+extern qboolean UI_IsFullscreen( void );
+extern void UI_SetActiveMenu( uiMenuCommand_t menu );
+extern void UI_PushMenu ( menuframework_s *menu );
+extern void UI_PopMenu (void);
+extern void UI_ForceMenuOff (void);
+extern char *UI_Argv( int arg );
+extern char *UI_Cvar_VariableString( const char *var_name );
+extern void UI_Refresh( int time );
+extern void UI_KeyEvent( int key );
+extern void UI_StartDemoLoop( void );
+extern qboolean m_entersound;
+void UI_LoadBestScores(const char *map, int game);
+extern uiStatic_t uis;
+
+//
+// ui_spLevel.c
+//
+void UI_SPLevelMenu_Cache( void );
+void UI_SPLevelMenu( void );
+void UI_SPLevelMenu_f( void );
+void UI_SPLevelMenu_ReInit( void );
+
+//
+// ui_spArena.c
+//
+void UI_SPArena_Start( const char *arenaInfo );
+
+//
+// ui_spPostgame.c
+//
+void UI_SPPostgameMenu_Cache( void );
+void UI_SPPostgameMenu_f( void );
+
+//
+// ui_spSkill.c
+//
+void UI_SPSkillMenu( const char *arenaInfo );
+void UI_SPSkillMenu_Cache( void );
+
+//
+// ui_syscalls.c
+//
+void trap_Print( const char *string );
+void trap_Error( const char *string );
+int trap_Milliseconds( void );
+void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
+void trap_Cvar_Update( vmCvar_t *vmCvar );
+void trap_Cvar_Set( const char *var_name, const char *value );
+float trap_Cvar_VariableValue( const char *var_name );
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+void trap_Cvar_SetValue( const char *var_name, float value );
+void trap_Cvar_Reset( const char *name );
+void trap_Cvar_Create( const char *var_name, const char *var_value, int flags );
+void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize );
+int trap_Argc( void );
+void trap_Argv( int n, char *buffer, int bufferLength );
+void trap_Cmd_ExecuteText( int exec_when, const char *text ); // don't use EXEC_NOW!
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
+void trap_FS_Read( void *buffer, int len, fileHandle_t f );
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f );
+void trap_FS_FCloseFile( fileHandle_t f );
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
+int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t
+qhandle_t trap_R_RegisterModel( const char *name );
+qhandle_t trap_R_RegisterSkin( const char *name );
+qhandle_t trap_R_RegisterShaderNoMip( const char *name );
+void trap_R_ClearScene( void );
+void trap_R_AddRefEntityToScene( const refEntity_t *re );
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts );
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void trap_R_RenderScene( const refdef_t *fd );
+void trap_R_SetColor( const float *rgba );
+void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader );
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs );
+void trap_UpdateScreen( void );
+int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName );
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum );
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed );
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen );
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen );
+void trap_Key_SetBinding( int keynum, const char *binding );
+qboolean trap_Key_IsDown( int keynum );
+qboolean trap_Key_GetOverstrikeMode( void );
+void trap_Key_SetOverstrikeMode( qboolean state );
+void trap_Key_ClearStates( void );
+int trap_Key_GetCatcher( void );
+void trap_Key_SetCatcher( int catcher );
+void trap_GetClipboardData( char *buf, int bufsize );
+void trap_GetClientState( uiClientState_t *state );
+void trap_GetGlconfig( glconfig_t *glconfig );
+int trap_GetConfigString( int index, char* buff, int buffsize );
+int trap_LAN_GetServerCount( int source );
+void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen );
+void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen );
+int trap_LAN_GetServerPing( int source, int n );
+int trap_LAN_GetPingQueueCount( void );
+void trap_LAN_ClearPing( int n );
+void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime );
+void trap_LAN_GetPingInfo( int n, char *buf, int buflen );
+void trap_LAN_LoadCachedServers( void );
+void trap_LAN_SaveCachedServers( void );
+void trap_LAN_MarkServerVisible(int source, int n, qboolean visible);
+int trap_LAN_ServerIsVisible( int source, int n);
+qboolean trap_LAN_UpdateVisiblePings( int source );
+int trap_LAN_AddServer(int source, const char *name, const char *addr);
+void trap_LAN_RemoveServer(int source, const char *addr);
+void trap_LAN_ResetPings(int n);
+int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen );
+int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 );
+int trap_MemoryRemaining( void );
+void trap_R_RegisterFont(const char *pFontname, int pointSize, fontInfo_t *font);
+void trap_S_StopBackgroundTrack( void );
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop);
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits);
+e_status trap_CIN_StopCinematic(int handle);
+e_status trap_CIN_RunCinematic (int handle);
+void trap_CIN_DrawCinematic (int handle);
+void trap_CIN_SetExtents (int handle, int x, int y, int w, int h);
+int trap_RealTime(qtime_t *qtime);
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset );
+
+void trap_SetPbClStatus( int status );
+
+//
+// ui_addbots.c
+//
+void UI_AddBots_Cache( void );
+void UI_AddBotsMenu( void );
+
+//
+// ui_removebots.c
+//
+void UI_RemoveBots_Cache( void );
+void UI_RemoveBotsMenu( void );
+
+//
+// ui_teamorders.c
+//
+extern void UI_TeamOrdersMenu( void );
+extern void UI_TeamOrdersMenu_f( void );
+extern void UI_TeamOrdersMenu_Cache( void );
+
+//
+// ui_loadconfig.c
+//
+void UI_LoadConfig_Cache( void );
+void UI_LoadConfigMenu( void );
+
+//
+// ui_saveconfig.c
+//
+void UI_SaveConfigMenu_Cache( void );
+void UI_SaveConfigMenu( void );
+
+//
+// ui_display.c
+//
+void UI_DisplayOptionsMenu_Cache( void );
+void UI_DisplayOptionsMenu( void );
+
+//
+// ui_sound.c
+//
+void UI_SoundOptionsMenu_Cache( void );
+void UI_SoundOptionsMenu( void );
+
+//
+// ui_network.c
+//
+void UI_NetworkOptionsMenu_Cache( void );
+void UI_NetworkOptionsMenu( void );
+
+//
+// ui_gameinfo.c
+//
+typedef enum {
+ AWARD_ACCURACY,
+ AWARD_IMPRESSIVE,
+ AWARD_EXCELLENT,
+ AWARD_GAUNTLET,
+ AWARD_FRAGS,
+ AWARD_PERFECT
+} awardType_t;
+
+const char *UI_GetArenaInfoByNumber( int num );
+const char *UI_GetArenaInfoByMap( const char *map );
+const char *UI_GetSpecialArenaInfo( const char *tag );
+int UI_GetNumArenas( void );
+int UI_GetNumSPArenas( void );
+int UI_GetNumSPTiers( void );
+
+char *UI_GetBotInfoByNumber( int num );
+char *UI_GetBotInfoByName( const char *name );
+int UI_GetNumBots( void );
+void UI_LoadBots( void );
+char *UI_GetBotNameByNumber( int num );
+
+void UI_GetBestScore( int level, int *score, int *skill );
+void UI_SetBestScore( int level, int score );
+int UI_TierCompleted( int levelWon );
+qboolean UI_ShowTierVideo( int tier );
+qboolean UI_CanShowTierVideo( int tier );
+int UI_GetCurrentGame( void );
+void UI_NewGame( void );
+void UI_LogAwardData( int award, int data );
+int UI_GetAwardLevel( int award );
+
+void UI_SPUnlock_f( void );
+void UI_SPUnlockMedals_f( void );
+
+void UI_InitGameinfo( void );
+
+//
+// ui_login.c
+//
+void Login_Cache( void );
+void UI_LoginMenu( void );
+
+//
+// ui_signup.c
+//
+void Signup_Cache( void );
+void UI_SignupMenu( void );
+
+//
+// ui_rankstatus.c
+//
+void RankStatus_Cache( void );
+void UI_RankStatusMenu( void );
+
+
+// new ui
+
+#define ASSET_BACKGROUND "uiBackground"
+
+// for tracking sp game info in Team Arena
+typedef struct postGameInfo_s {
+ int score;
+ int redScore;
+ int blueScore;
+ int perfects;
+ int accuracy;
+ int impressives;
+ int excellents;
+ int defends;
+ int assists;
+ int gauntlets;
+ int captures;
+ int time;
+ int timeBonus;
+ int shutoutBonus;
+ int skillBonus;
+ int baseScore;
+} postGameInfo_t;
+
+
+
+#endif
diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c
new file mode 100644
index 0000000..e2396a9
--- /dev/null
+++ b/src/ui/ui_main.c
@@ -0,0 +1,6530 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*
+=======================================================================
+
+USER INTERFACE MAIN
+
+=======================================================================
+*/
+
+// use this to get a demo build without an explicit demo build, i.e. to get the demo ui files to build
+//#define PRE_RELEASE_TADEMO
+
+#include "ui_local.h"
+
+uiInfo_t uiInfo;
+
+#ifdef MODULE_INTERFACE_11
+#undef AS_GLOBAL
+#undef AS_LOCAL
+#define AS_GLOBAL 2
+#define AS_LOCAL 0
+#endif
+
+static const char *MonthAbbrev[] = {
+ "Jan","Feb","Mar",
+ "Apr","May","Jun",
+ "Jul","Aug","Sep",
+ "Oct","Nov","Dec"
+};
+
+
+static const char *skillLevels[] = {
+ "I Can Win",
+ "Bring It On",
+ "Hurt Me Plenty",
+ "Hardcore",
+ "Nightmare"
+};
+
+static const int numSkillLevels = sizeof(skillLevels) / sizeof(const char*);
+
+
+static const char *netSources[] = {
+#ifdef MODULE_INTERFACE_11
+ "LAN",
+ "Mplayer",
+ "Internet",
+#else
+ "Internet",
+ "Mplayer",
+ "LAN",
+#endif
+ "Favorites"
+};
+static const int numNetSources = sizeof(netSources) / sizeof(const char*);
+
+static const serverFilter_t serverFilters[] = {
+ {"All", "" },
+ {"Quake 3 Arena", "" },
+ {"Team Arena", "missionpack" },
+ {"Rocket Arena", "arena" },
+ {"Alliance", "alliance20" },
+ {"Weapons Factory Arena", "wfa" },
+ {"OSP", "osp" },
+};
+
+static const int numServerFilters = sizeof(serverFilters) / sizeof(serverFilter_t);
+
+static char* netnames[] = {
+ "???",
+ "UDP",
+ "IPX",
+ NULL
+};
+
+static int gamecodetoui[] = {4,2,3,0,5,1,6};
+
+
+static void UI_StartServerRefresh(qboolean full);
+static void UI_StopServerRefresh( void );
+static void UI_DoServerRefresh( void );
+static void UI_FeederSelection(float feederID, int index);
+static void UI_BuildServerDisplayList(int force);
+static void UI_BuildServerStatus(qboolean force);
+static void UI_BuildFindPlayerList(qboolean force);
+static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 );
+static int UI_MapCountByGameType(qboolean singlePlayer);
+static int UI_HeadCountByTeam( void );
+static const char *UI_SelectedMap(int index, int *actual);
+static const char *UI_SelectedHead(int index, int *actual);
+static int UI_GetIndexFromSelection(int actual);
+
+int ProcessNewUI( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 );
+
+/*
+================
+vmMain
+
+This is the only way control passes into the module.
+This must be the very first function compiled into the .qvm file
+================
+*/
+vmCvar_t ui_new;
+vmCvar_t ui_debug;
+vmCvar_t ui_initialized;
+vmCvar_t ui_teamArenaFirstRun;
+
+void _UI_Init( qboolean );
+void _UI_Shutdown( void );
+void _UI_KeyEvent( int key, qboolean down );
+void _UI_MouseEvent( int dx, int dy );
+int _UI_MousePosition( void );
+void _UI_SetMousePosition( int x, int y );
+void _UI_Refresh( int realtime );
+qboolean _UI_IsFullscreen( void );
+Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3,
+ int arg4, int arg5, int arg6, int arg7,
+ int arg8, int arg9, int arg10, int arg11 ) {
+ switch ( command ) {
+ case UI_GETAPIVERSION:
+ return UI_API_VERSION;
+
+ case UI_INIT:
+ _UI_Init(arg0);
+ return 0;
+
+ case UI_SHUTDOWN:
+ _UI_Shutdown();
+ return 0;
+
+ case UI_KEY_EVENT:
+ _UI_KeyEvent( arg0, arg1 );
+ return 0;
+
+ case UI_MOUSE_EVENT:
+ _UI_MouseEvent( arg0, arg1 );
+ return 0;
+
+#ifndef MODULE_INTERFACE_11
+ case UI_MOUSE_POSITION:
+ return _UI_MousePosition( );
+
+ case UI_SET_MOUSE_POSITION:
+ _UI_SetMousePosition( arg0, arg1 );
+ return 0;
+#endif
+
+ case UI_REFRESH:
+ _UI_Refresh( arg0 );
+ return 0;
+
+ case UI_IS_FULLSCREEN:
+ return _UI_IsFullscreen();
+
+ case UI_SET_ACTIVE_MENU:
+ _UI_SetActiveMenu( arg0 );
+ return 0;
+
+ case UI_CONSOLE_COMMAND:
+ return UI_ConsoleCommand(arg0);
+
+ case UI_DRAW_CONNECT_SCREEN:
+ UI_DrawConnectScreen( arg0 );
+ return 0;
+ }
+
+ return -1;
+}
+
+
+
+void AssetCache( void ) {
+ uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR );
+ uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR );
+ uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN );
+ uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP );
+ uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT );
+ uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT );
+ uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB );
+ uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR );
+ uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB );
+}
+
+void _UI_DrawSides(float x, float y, float w, float h, float size) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ size *= uiInfo.uiDC.xscale;
+ trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+
+void _UI_DrawTopBottom(float x, float y, float w, float h, float size) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ size *= uiInfo.uiDC.yscale;
+ trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+/*
+================
+UI_DrawRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) {
+ trap_R_SetColor( color );
+
+ _UI_DrawTopBottom(x, y, width, height, size);
+ _UI_DrawSides(x, y, width, height, size);
+
+ trap_R_SetColor( NULL );
+}
+
+
+
+
+int Text_Width(const char *text, float scale, int limit) {
+ int count,len;
+ float out;
+ glyphInfo_t *glyph;
+ float useScale;
+ const char *s = text;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ out = 0;
+ if (text) {
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ if ( Q_IsColorString(s) ) {
+ s += 2;
+ continue;
+ } else {
+ glyph = &font->glyphs[(int)*s];
+ out += glyph->xSkip;
+ s++;
+ count++;
+ }
+ }
+ }
+ return out * useScale;
+}
+
+int Text_Height(const char *text, float scale, int limit) {
+ int len, count;
+ float max;
+ glyphInfo_t *glyph;
+ float useScale;
+ const char *s = text; // bk001206 - unsigned
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ max = 0;
+ if (text) {
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ if ( Q_IsColorString(s) ) {
+ s += 2;
+ continue;
+ } else {
+ glyph = &font->glyphs[(int)*s];
+ if (max < glyph->height) {
+ max = glyph->height;
+ }
+ s++;
+ count++;
+ }
+ }
+ }
+ return max * useScale;
+}
+
+void Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) {
+ float w, h;
+ w = width * scale;
+ h = height * scale;
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader );
+}
+
+void Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) {
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+ float useScale;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ if (text) {
+ const char *s = text; // bk001206 - unsigned
+ trap_R_SetColor( color );
+ memcpy(&newColor[0], &color[0], sizeof(vec4_t));
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ glyph = &font->glyphs[(int)*s];
+ //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top;
+ //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height);
+ if ( Q_IsColorString( s ) ) {
+ memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) );
+ newColor[3] = color[3];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ } else {
+ float yadj = useScale * glyph->top;
+ if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) {
+ int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;
+ colorBlack[3] = newColor[3];
+ trap_R_SetColor( colorBlack );
+ Text_PaintChar(x + ofs, y - yadj + ofs,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+ trap_R_SetColor( newColor );
+ colorBlack[3] = 1.0;
+ }
+ else if( style == ITEM_TEXTSTYLE_NEON )
+ {
+ vec4_t glow, outer, inner, white;
+
+ glow[ 0 ] = newColor[ 0 ] * 0.5;
+ glow[ 1 ] = newColor[ 1 ] * 0.5;
+ glow[ 2 ] = newColor[ 2 ] * 0.5;
+ glow[ 3 ] = newColor[ 3 ] * 0.2;
+
+ outer[ 0 ] = newColor[ 0 ];
+ outer[ 1 ] = newColor[ 1 ];
+ outer[ 2 ] = newColor[ 2 ];
+ outer[ 3 ] = newColor[ 3 ];
+
+ inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5;
+ inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5;
+ inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5;
+ inner[ 3 ] = newColor[ 3 ];
+
+ white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f;
+
+ trap_R_SetColor( glow );
+ Text_PaintChar( x - 1.5, y - yadj - 1.5,
+ glyph->imageWidth + 3,
+ glyph->imageHeight + 3,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( outer );
+ Text_PaintChar( x - 1, y - yadj - 1,
+ glyph->imageWidth + 2,
+ glyph->imageHeight + 2,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( inner );
+ Text_PaintChar( x - 0.5, y - yadj - 0.5,
+ glyph->imageWidth + 1,
+ glyph->imageHeight + 1,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( white );
+ }
+
+ Text_PaintChar(x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+
+ x += (glyph->xSkip * useScale) + adjust;
+ s++;
+ count++;
+ }
+ }
+ trap_R_SetColor( NULL );
+ }
+}
+
+void Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) {
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph, *glyph2;
+ float yadj;
+ float useScale;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ if (text) {
+ const char *s = text; // bk001206 - unsigned
+ trap_R_SetColor( color );
+ memcpy(&newColor[0], &color[0], sizeof(vec4_t));
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ glyph2 = &font->glyphs[ (int) cursor]; // bk001206 - possible signed char
+ while (s && *s && count < len) {
+ glyph = &font->glyphs[(int)*s];
+ if ( Q_IsColorString( s ) ) {
+ memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) );
+ newColor[3] = color[3];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ } else {
+ yadj = useScale * glyph->top;
+ if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) {
+ int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;
+ colorBlack[3] = newColor[3];
+ trap_R_SetColor( colorBlack );
+ Text_PaintChar(x + ofs, y - yadj + ofs,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+ colorBlack[3] = 1.0;
+ trap_R_SetColor( newColor );
+ }
+ else if( style == ITEM_TEXTSTYLE_NEON )
+ {
+ vec4_t glow, outer, inner, white;
+
+ glow[ 0 ] = newColor[ 0 ] * 0.5;
+ glow[ 1 ] = newColor[ 1 ] * 0.5;
+ glow[ 2 ] = newColor[ 2 ] * 0.5;
+ glow[ 3 ] = newColor[ 3 ] * 0.2;
+
+ outer[ 0 ] = newColor[ 0 ];
+ outer[ 1 ] = newColor[ 1 ];
+ outer[ 2 ] = newColor[ 2 ];
+ outer[ 3 ] = newColor[ 3 ];
+
+ inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5;
+ inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5;
+ inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5;
+ inner[ 3 ] = newColor[ 3 ];
+
+ white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f;
+
+ trap_R_SetColor( glow );
+ Text_PaintChar( x - 1.5, y - yadj - 1.5,
+ glyph->imageWidth + 3,
+ glyph->imageHeight + 3,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( outer );
+ Text_PaintChar( x - 1, y - yadj - 1,
+ glyph->imageWidth + 2,
+ glyph->imageHeight + 2,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( inner );
+ Text_PaintChar( x - 0.5, y - yadj - 0.5,
+ glyph->imageWidth + 1,
+ glyph->imageHeight + 1,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( white );
+ }
+
+ Text_PaintChar(x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+
+ // CG_DrawPic(x, y - yadj, scale * uiDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * uiDC.Assets.textFont.glyphs[text[i]].imageHeight, uiDC.Assets.textFont.glyphs[text[i]].glyph);
+ yadj = useScale * glyph2->top;
+ if (count == cursorPos && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) {
+ Text_PaintChar(x, y - yadj,
+ glyph2->imageWidth,
+ glyph2->imageHeight,
+ useScale,
+ glyph2->s,
+ glyph2->t,
+ glyph2->s2,
+ glyph2->t2,
+ glyph2->glyph);
+ }
+
+ x += (glyph->xSkip * useScale);
+ s++;
+ count++;
+ }
+ }
+ // need to paint cursor at end of text
+ if (cursorPos == len && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) {
+ yadj = useScale * glyph2->top;
+ Text_PaintChar(x, y - yadj,
+ glyph2->imageWidth,
+ glyph2->imageHeight,
+ useScale,
+ glyph2->s,
+ glyph2->t,
+ glyph2->s2,
+ glyph2->t2,
+ glyph2->glyph);
+
+ }
+
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+
+static void Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) {
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+ if (text) {
+ const char *s = text; // bk001206 - unsigned
+ float max = *maxX;
+ float useScale;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale > ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ trap_R_SetColor( color );
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ glyph = &font->glyphs[(int)*s];
+ if ( Q_IsColorString( s ) ) {
+ memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) );
+ newColor[3] = color[3];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ } else {
+ float yadj = useScale * glyph->top;
+ if (Text_Width(s, useScale, 1) + x > max) {
+ *maxX = 0;
+ break;
+ }
+ Text_PaintChar(x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+ x += (glyph->xSkip * useScale) + adjust;
+ *maxX = x;
+ count++;
+ s++;
+ }
+ }
+ trap_R_SetColor( NULL );
+ }
+
+}
+
+
+void UI_ShowPostGame(qboolean newHigh) {
+ trap_Cvar_Set ("cg_cameraOrbit", "0");
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set( "sv_killserver", "1" );
+ uiInfo.soundHighScore = newHigh;
+ _UI_SetActiveMenu(UIMENU_POSTGAME);
+}
+/*
+=================
+_UI_Refresh
+=================
+*/
+
+void UI_DrawCenteredPic(qhandle_t image, int w, int h) {
+ int x, y;
+ x = (SCREEN_WIDTH - w) / 2;
+ y = (SCREEN_HEIGHT - h) / 2;
+ UI_DrawHandlePic(x, y, w, h, image);
+}
+
+int frameCount = 0;
+int startTime;
+
+#define UI_FPS_FRAMES 4
+void _UI_Refresh( int realtime )
+{
+ static int index;
+ static int previousTimes[UI_FPS_FRAMES];
+
+ //if ( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) {
+ // return;
+ //}
+
+ uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime;
+ uiInfo.uiDC.realTime = realtime;
+
+ previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime;
+ index++;
+ if ( index > UI_FPS_FRAMES ) {
+ int i, total;
+ // average multiple frames together to smooth changes out a bit
+ total = 0;
+ for ( i = 0 ; i < UI_FPS_FRAMES ; i++ ) {
+ total += previousTimes[i];
+ }
+ if ( !total ) {
+ total = 1;
+ }
+ uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total;
+ }
+
+
+
+ UI_UpdateCvars();
+
+ if (Menu_Count() > 0) {
+ // paint all the menus
+ Menu_PaintAll();
+ // refresh server browser list
+ UI_DoServerRefresh();
+ // refresh server status
+ UI_BuildServerStatus(qfalse);
+ // refresh find player list
+ UI_BuildFindPlayerList(qfalse);
+ }
+
+ // draw cursor
+ UI_SetColor( NULL );
+
+ //TA: don't draw the cursor whilst loading
+ if( Menu_Count( ) > 0 && !trap_Cvar_VariableValue( "ui_loading" ) && !trap_Cvar_VariableValue( "ui_hideCursor" ) )
+ UI_DrawHandlePic( uiInfo.uiDC.cursorx-16, uiInfo.uiDC.cursory-16, 32, 32, uiInfo.uiDC.Assets.cursor);
+
+#ifndef NDEBUG
+ if (uiInfo.uiDC.debug)
+ {
+ // cursor coordinates
+ //FIXME
+ //UI_DrawString( 0, 0, va("(%d,%d)",uis.cursorx,uis.cursory), UI_LEFT|UI_SMALLFONT, colorRed );
+ }
+#endif
+
+}
+
+/*
+=================
+_UI_Shutdown
+=================
+*/
+void _UI_Shutdown( void ) {
+ trap_LAN_SaveCachedServers();
+}
+
+char *defaultMenu = NULL;
+
+char *GetMenuBuffer(const char *filename) {
+ int len;
+ fileHandle_t f;
+ static char buf[MAX_MENUFILE];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( !f ) {
+ trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) );
+ return defaultMenu;
+ }
+ if ( len >= MAX_MENUFILE ) {
+ trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) );
+ trap_FS_FCloseFile( f );
+ return defaultMenu;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+ //COM_Compress(buf);
+ return buf;
+
+}
+
+qboolean Asset_Parse(int handle) {
+ pc_token_t token;
+ const char *tempStr;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (Q_stricmp(token.string, "{") != 0) {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+
+ memset(&token, 0, sizeof(pc_token_t));
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ if (Q_stricmp(token.string, "}") == 0) {
+ return qtrue;
+ }
+
+ // font
+ if (Q_stricmp(token.string, "font") == 0) {
+ int pointSize;
+ if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) {
+ return qfalse;
+ }
+ trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.textFont);
+ uiInfo.uiDC.Assets.fontRegistered = qtrue;
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "smallFont") == 0) {
+ int pointSize;
+ if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) {
+ return qfalse;
+ }
+ trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont);
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "bigFont") == 0) {
+ int pointSize;
+ if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) {
+ return qfalse;
+ }
+ trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont);
+ continue;
+ }
+
+
+ // gradientbar
+ if (Q_stricmp(token.string, "gradientbar") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr);
+ continue;
+ }
+
+ // enterMenuSound
+ if (Q_stricmp(token.string, "menuEnterSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // exitMenuSound
+ if (Q_stricmp(token.string, "menuExitSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // itemFocusSound
+ if (Q_stricmp(token.string, "itemFocusSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // menuBuzzSound
+ if (Q_stricmp(token.string, "menuBuzzSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "cursor") == 0) {
+ if (!PC_String_Parse(handle, &uiInfo.uiDC.Assets.cursorStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr);
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "fadeClamp") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeClamp)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "fadeCycle") == 0) {
+ if (!PC_Int_Parse(handle, &uiInfo.uiDC.Assets.fadeCycle)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "fadeAmount") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeAmount)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "shadowX") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowX)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "shadowY") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowY)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "shadowColor") == 0) {
+ if (!PC_Color_Parse(handle, &uiInfo.uiDC.Assets.shadowColor)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3];
+ continue;
+ }
+
+ }
+ return qfalse;
+}
+
+void Font_Report( void ) {
+ int i;
+ Com_Printf("Font Info\n");
+ Com_Printf("=========\n");
+ for ( i = 32; i < 96; i++) {
+ Com_Printf("Glyph handle %i: %i\n", i, uiInfo.uiDC.Assets.textFont.glyphs[i].glyph);
+ }
+}
+
+void UI_Report( void ) {
+ String_Report();
+ //Font_Report();
+
+}
+
+void UI_ParseMenu(const char *menuFile) {
+ int handle;
+ pc_token_t token;
+
+ /*Com_Printf("Parsing menu file:%s\n", menuFile);*/
+
+ handle = trap_Parse_LoadSource(menuFile);
+ if (!handle) {
+ return;
+ }
+
+ while ( 1 ) {
+ memset(&token, 0, sizeof(pc_token_t));
+ if (!trap_Parse_ReadToken( handle, &token )) {
+ break;
+ }
+
+ //if ( Q_stricmp( token, "{" ) ) {
+ // Com_Printf( "Missing { in menu file\n" );
+ // break;
+ //}
+
+ //if ( menuCount == MAX_MENUS ) {
+ // Com_Printf( "Too many menus!\n" );
+ // break;
+ //}
+
+ if ( token.string[0] == '}' ) {
+ break;
+ }
+
+ if (Q_stricmp(token.string, "assetGlobalDef") == 0) {
+ if (Asset_Parse(handle)) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ if (Q_stricmp(token.string, "menudef") == 0) {
+ // start a new menu
+ Menu_New(handle);
+ }
+ }
+ trap_Parse_FreeSource(handle);
+}
+
+/*
+===============
+UI_FindInfoPaneByName
+===============
+*/
+tremInfoPane_t *UI_FindInfoPaneByName( const char *name )
+{
+ int i;
+
+ for( i = 0; i < uiInfo.tremInfoPaneCount; i++ )
+ {
+ if( !Q_stricmp( uiInfo.tremInfoPanes[ i ].name, name ) )
+ return &uiInfo.tremInfoPanes[ i ];
+ }
+
+ //create a dummy infopane demanding the user write the infopane
+ uiInfo.tremInfoPanes[ i ].name = String_Alloc( name );
+ strncpy( uiInfo.tremInfoPanes[ i ].text, "Not implemented.\n\nui/infopanes.def\n", MAX_INFOPANE_TEXT );
+ Q_strcat( uiInfo.tremInfoPanes[ i ].text, MAX_INFOPANE_TEXT, String_Alloc( name ) );
+
+ uiInfo.tremInfoPaneCount++;
+
+ return &uiInfo.tremInfoPanes[ i ];
+}
+
+/*
+===============
+UI_LoadInfoPane
+===============
+*/
+qboolean UI_LoadInfoPane( int handle )
+{
+ pc_token_t token;
+ qboolean valid = qfalse;
+
+ while( 1 )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( !Q_stricmp( token.string, "name" ) )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].name = String_Alloc( token.string );
+ valid = qtrue;
+ }
+ else if( !Q_stricmp( token.string, "graphic" ) )
+ {
+ int *graphic;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ graphic = &uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].numGraphics;
+
+ if( !Q_stricmp( token.string, "top" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_TOP;
+ else if( !Q_stricmp( token.string, "bottom" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_BOTTOM;
+ else if( !Q_stricmp( token.string, "left" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_LEFT;
+ else if( !Q_stricmp( token.string, "right" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_RIGHT;
+ else
+ break;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( !Q_stricmp( token.string, "center" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = -1;
+ else
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = token.intvalue;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].graphic =
+ trap_R_RegisterShaderNoMip( token.string );
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].width = token.intvalue;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].height = token.intvalue;
+
+ //increment graphics
+ (*graphic)++;
+
+ if( *graphic == MAX_INFOPANE_GRAPHICS )
+ break;
+ }
+ else if( !Q_stricmp( token.string, "text" ) )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ Q_strcat( uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].text, MAX_INFOPANE_TEXT, token.string );
+ }
+ else if( !Q_stricmp( token.string, "align" ) )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( !Q_stricmp( token.string, "left" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_LEFT;
+ else if( !Q_stricmp( token.string, "right" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_RIGHT;
+ else if( !Q_stricmp( token.string, "center" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_CENTER;
+ }
+ else if( token.string[ 0 ] == '}' )
+ {
+ //reached the end, break
+ break;
+ }
+ else
+ break;
+ }
+
+ if( valid )
+ {
+ uiInfo.tremInfoPaneCount++;
+ return qtrue;
+ }
+ else
+ {
+ return qfalse;
+ }
+}
+
+/*
+===============
+UI_LoadInfoPanes
+===============
+*/
+void UI_LoadInfoPanes( const char *file )
+{
+ pc_token_t token;
+ int handle;
+ int count;
+
+ uiInfo.tremInfoPaneCount = count = 0;
+
+ handle = trap_Parse_LoadSource( file );
+
+ if( !handle )
+ {
+ trap_Error( va( S_COLOR_YELLOW "infopane file not found: %s\n", file ) );
+ return;
+ }
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( token.string[ 0 ] == 0 )
+ break;
+
+ if( token.string[ 0 ] == '{' )
+ {
+ if( UI_LoadInfoPane( handle ) )
+ count++;
+
+ if( count == MAX_INFOPANES )
+ break;
+ }
+ }
+
+ trap_Parse_FreeSource( handle );
+}
+
+qboolean Load_Menu(int handle) {
+ pc_token_t token;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (token.string[0] != '{') {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ if ( token.string[0] == 0 ) {
+ return qfalse;
+ }
+
+ if ( token.string[0] == '}' ) {
+ return qtrue;
+ }
+
+ UI_ParseMenu(token.string);
+ }
+ return qfalse;
+}
+
+void UI_LoadMenus(const char *menuFile, qboolean reset) {
+ pc_token_t token;
+ int handle;
+ int start;
+
+ start = trap_Milliseconds();
+
+ handle = trap_Parse_LoadSource( menuFile );
+ if (!handle) {
+ trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) );
+ handle = trap_Parse_LoadSource( "ui/menus.txt" );
+ if (!handle) {
+ trap_Error( va( S_COLOR_RED "default menu file not found: ui/menus.txt, unable to continue!\n" ) );
+ }
+ }
+
+ ui_new.integer = 1;
+
+ if (reset) {
+ Menu_Reset();
+ }
+
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token))
+ break;
+ if( token.string[0] == 0 || token.string[0] == '}') {
+ break;
+ }
+
+ if ( token.string[0] == '}' ) {
+ break;
+ }
+
+ if (Q_stricmp(token.string, "loadmenu") == 0) {
+ if (Load_Menu(handle)) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ }
+
+ Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start);
+
+ trap_Parse_FreeSource( handle );
+}
+
+void UI_Load( void ) {
+ char lastName[1024];
+ menuDef_t *menu = Menu_GetFocused();
+ char *menuSet = UI_Cvar_VariableString("ui_menuFiles");
+ if (menu && menu->window.name) {
+ strcpy(lastName, menu->window.name);
+ }
+ if (menuSet == NULL || menuSet[0] == '\0') {
+ menuSet = "ui/menus.txt";
+ }
+
+ String_Init();
+
+/* UI_ParseGameInfo("gameinfo.txt");
+ UI_LoadArenas();*/
+
+ UI_LoadMenus(menuSet, qtrue);
+ Menus_CloseAll();
+ Menus_ActivateByName(lastName);
+
+}
+
+static const char *handicapValues[] = {"None","95","90","85","80","75","70","65","60","55","50","45","40","35","30","25","20","15","10","5",NULL};
+
+static void UI_DrawHandicap(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i, h;
+
+ h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") );
+ i = 20 - h / 5;
+
+ Text_Paint(rect->x, rect->y, scale, color, handicapValues[i], 0, 0, textStyle);
+}
+
+static void UI_DrawClanName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_teamName"), 0, 0, textStyle);
+}
+
+
+static void UI_SetCapFragLimits(qboolean uiVars) {
+ int cap = 5;
+ int frag = 10;
+ if (uiVars) {
+ trap_Cvar_Set("ui_captureLimit", va("%d", cap));
+ trap_Cvar_Set("ui_fragLimit", va("%d", frag));
+ } else {
+ trap_Cvar_Set("capturelimit", va("%d", cap));
+ trap_Cvar_Set("fraglimit", va("%d", frag));
+ }
+}
+// ui_gameType assumes gametype 0 is -1 ALL and will not show
+static void UI_DrawGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_gameType.integer].gameType, 0, 0, textStyle);
+}
+
+static void UI_DrawNetGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_netGameType.integer < 0 || ui_netGameType.integer > uiInfo.numGameTypes) {
+ trap_Cvar_Set("ui_netGameType", "0");
+ trap_Cvar_Set("ui_actualNetGameType", "0");
+ }
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_netGameType.integer].gameType , 0, 0, textStyle);
+}
+
+static void UI_DrawJoinGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_joinGameType.integer < 0 || ui_joinGameType.integer > uiInfo.numJoinGameTypes) {
+ trap_Cvar_Set("ui_joinGameType", "0");
+ }
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.joinGameTypes[ui_joinGameType.integer].gameType , 0, 0, textStyle);
+}
+
+
+
+static int UI_TeamIndexFromName(const char *name) {
+ int i;
+
+ if (name && *name) {
+ for (i = 0; i < uiInfo.teamCount; i++) {
+ if (Q_stricmp(name, uiInfo.teamList[i].teamName) == 0) {
+ return i;
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+static void UI_DrawClanLogo(rectDef_t *rect, float scale, vec4_t color) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ trap_R_SetColor( color );
+
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon);
+ trap_R_SetColor(NULL);
+ }
+}
+
+static void UI_DrawClanCinematic(rectDef_t *rect, float scale, vec4_t color) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+
+ if (uiInfo.teamList[i].cinematic >= -2) {
+ if (uiInfo.teamList[i].cinematic == -1) {
+ uiInfo.teamList[i].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.teamList[i].imageName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+ if (uiInfo.teamList[i].cinematic >= 0) {
+ trap_CIN_RunCinematic(uiInfo.teamList[i].cinematic);
+ trap_CIN_SetExtents(uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.teamList[i].cinematic);
+ } else {
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal);
+ trap_R_SetColor(NULL);
+ uiInfo.teamList[i].cinematic = -2;
+ }
+ } else {
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon);
+ trap_R_SetColor(NULL);
+ }
+ }
+
+}
+
+static void UI_DrawPreviewCinematic(rectDef_t *rect, float scale, vec4_t color) {
+ if (uiInfo.previewMovie > -2) {
+ uiInfo.previewMovie = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.movieList[uiInfo.movieIndex]), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ if (uiInfo.previewMovie >= 0) {
+ trap_CIN_RunCinematic(uiInfo.previewMovie);
+ trap_CIN_SetExtents(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.previewMovie);
+ } else {
+ uiInfo.previewMovie = -2;
+ }
+ }
+
+}
+
+
+#define GRAPHIC_BWIDTH 8.0f
+/*
+===============
+UI_DrawInfoPane
+===============
+*/
+static void UI_DrawInfoPane( tremInfoPane_t *pane, rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int textStyle )
+{
+ int i;
+ float maxLeft = 0, maxTop = 0;
+ float maxRight = 0, maxBottom = 0;
+ float x = rect->x - text_x, y = rect->y - text_y, w, h;
+ float xoffset = 0, yoffset = 0;
+ menuDef_t dummyParent;
+ itemDef_t textItem;
+
+ //iterate through graphics
+ for( i = 0; i < pane->numGraphics; i++ )
+ {
+ float width = pane->graphics[ i ].width;
+ float height = pane->graphics[ i ].height;
+ qhandle_t graphic = pane->graphics[ i ].graphic;
+
+ if( pane->graphics[ i ].side == INFOPANE_TOP || pane->graphics[ i ].side == INFOPANE_BOTTOM )
+ {
+ //set horizontal offset of graphic
+ if( pane->graphics[ i ].offset < 0 )
+ xoffset = ( rect->w / 2 ) - ( pane->graphics[ i ].width / 2 );
+ else
+ xoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_LEFT || pane->graphics[ i ].side == INFOPANE_RIGHT )
+ {
+ //set vertical offset of graphic
+ if( pane->graphics[ i ].offset < 0 )
+ yoffset = ( rect->h / 2 ) - ( pane->graphics[ i ].height / 2 );
+ else
+ yoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH;
+ }
+
+ if( pane->graphics[ i ].side == INFOPANE_LEFT )
+ {
+ //set the horizontal offset of the text
+ if( pane->graphics[ i ].width > maxLeft )
+ maxLeft = pane->graphics[ i ].width + GRAPHIC_BWIDTH;
+
+ xoffset = GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_RIGHT )
+ {
+ if( pane->graphics[ i ].width > maxRight )
+ maxRight = pane->graphics[ i ].width + GRAPHIC_BWIDTH;
+
+ xoffset = rect->w - width - GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_TOP )
+ {
+ //set the vertical offset of the text
+ if( pane->graphics[ i ].height > maxTop )
+ maxTop = pane->graphics[ i ].height + GRAPHIC_BWIDTH;
+
+ yoffset = GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_BOTTOM )
+ {
+ if( pane->graphics[ i ].height > maxBottom )
+ maxBottom = pane->graphics[ i ].height + GRAPHIC_BWIDTH;
+
+ yoffset = rect->h - height - GRAPHIC_BWIDTH;
+ }
+
+ //draw the graphic
+ UI_DrawHandlePic( x + xoffset, y + yoffset, width, height, graphic );
+ }
+
+ //offset the text
+ x = rect->x + maxLeft;
+ y = rect->y + maxTop;
+ w = rect->w - ( maxLeft + maxRight + 16 + ( 2 * text_x ) ); //16 to ensure text within frame
+ h = rect->h - ( maxTop + maxBottom );
+
+ textItem.text = pane->text;
+
+ textItem.parent = &dummyParent;
+ memcpy( textItem.window.foreColor, color, sizeof( vec4_t ) );
+ textItem.window.flags = 0;
+
+ switch( pane->align )
+ {
+ case ITEM_ALIGN_LEFT:
+ textItem.window.rect.x = x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ textItem.window.rect.x = x + w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ textItem.window.rect.x = x + ( w / 2 );
+ break;
+
+ default:
+ textItem.window.rect.x = x;
+ break;
+ }
+
+ textItem.window.rect.y = y;
+ textItem.window.rect.w = w;
+ textItem.window.rect.h = h;
+ textItem.window.borderSize = 0;
+ textItem.textRect.x = 0;
+ textItem.textRect.y = 0;
+ textItem.textRect.w = 0;
+ textItem.textRect.h = 0;
+ textItem.textalignment = pane->align;
+ textItem.textalignx = text_x;
+ textItem.textaligny = text_y;
+ textItem.textscale = scale;
+ textItem.textStyle = textStyle;
+
+ textItem.enableCvar = NULL;
+ textItem.cvarTest = NULL;
+
+ //hack to utilise existing autowrap code
+ Item_Text_AutoWrapped_Paint( &textItem );
+}
+
+
+static void UI_DrawSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i;
+ i = trap_Cvar_VariableValue( "g_spSkill" );
+ if (i < 1 || i > numSkillLevels) {
+ i = 1;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, skillLevels[i-1],0, 0, textStyle);
+}
+
+
+static void UI_DrawTeamName(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int textStyle) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ Text_Paint(rect->x, rect->y, scale, color, va("%s: %s", (blue) ? "Blue" : "Red", uiInfo.teamList[i].teamName),0, 0, textStyle);
+ }
+}
+
+static void UI_DrawTeamMember(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int num, int textStyle) {
+ // 0 - None
+ // 1 - Human
+ // 2..NumCharacters - Bot
+ int value = trap_Cvar_VariableValue(va(blue ? "ui_blueteam%i" : "ui_redteam%i", num));
+ const char *text;
+ if (value <= 0) {
+ text = "Closed";
+ } else if (value == 1) {
+ text = "Human";
+ } else {
+ value -= 2;
+
+ if( value >= UI_GetNumBots( ) )
+ value = 0;
+
+ text = UI_GetBotNameByNumber(value);
+ }
+ Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle);
+}
+
+static void UI_DrawMapPreview(rectDef_t *rect, float scale, vec4_t color, qboolean net) {
+ int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (map < 0 || map > uiInfo.mapCount) {
+ if (net) {
+ ui_currentNetMap.integer = 0;
+ trap_Cvar_Set("ui_currentNetMap", "0");
+ } else {
+ ui_currentMap.integer = 0;
+ trap_Cvar_Set("ui_currentMap", "0");
+ }
+ map = 0;
+ }
+
+ if (uiInfo.mapList[map].levelShot == -1) {
+ uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[map].imageName);
+ }
+
+ if (uiInfo.mapList[map].levelShot > 0) {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot);
+ } else {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen"));
+ }
+}
+
+
+static void UI_DrawMapTimeToBeat(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int minutes, seconds, time;
+ if (ui_currentMap.integer < 0 || ui_currentMap.integer > uiInfo.mapCount) {
+ ui_currentMap.integer = 0;
+ trap_Cvar_Set("ui_currentMap", "0");
+ }
+
+ time = uiInfo.mapList[ui_currentMap.integer].timeToBeat[uiInfo.gameTypes[ui_gameType.integer].gtEnum];
+
+ minutes = time / 60;
+ seconds = time % 60;
+
+ Text_Paint(rect->x, rect->y, scale, color, va("%02i:%02i", minutes, seconds), 0, 0, textStyle);
+}
+
+
+
+static void UI_DrawMapCinematic(rectDef_t *rect, float scale, vec4_t color, qboolean net) {
+
+ int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (map < 0 || map > uiInfo.mapCount) {
+ if (net) {
+ ui_currentNetMap.integer = 0;
+ trap_Cvar_Set("ui_currentNetMap", "0");
+ } else {
+ ui_currentMap.integer = 0;
+ trap_Cvar_Set("ui_currentMap", "0");
+ }
+ map = 0;
+ }
+
+ if (uiInfo.mapList[map].cinematic >= -1) {
+ if (uiInfo.mapList[map].cinematic == -1) {
+ uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[map].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+ if (uiInfo.mapList[map].cinematic >= 0) {
+ trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic);
+ trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic);
+ } else {
+ uiInfo.mapList[map].cinematic = -2;
+ }
+ } else {
+ UI_DrawMapPreview(rect, scale, color, net);
+ }
+}
+
+
+
+static qboolean updateModel = qtrue;
+static qboolean q3Model = qfalse;
+
+static void UI_DrawPlayerModel(rectDef_t *rect) {
+ static playerInfo_t info;
+ char model[MAX_QPATH];
+ char team[256];
+ char head[256];
+ vec3_t viewangles;
+ vec3_t moveangles;
+
+ if (trap_Cvar_VariableValue("ui_Q3Model")) {
+ strcpy(model, UI_Cvar_VariableString("model"));
+ strcpy(head, UI_Cvar_VariableString("headmodel"));
+ if (!q3Model) {
+ q3Model = qtrue;
+ updateModel = qtrue;
+ }
+ team[0] = '\0';
+ } else {
+
+ strcpy(team, UI_Cvar_VariableString("ui_teamName"));
+ strcpy(model, UI_Cvar_VariableString("team_model"));
+ strcpy(head, UI_Cvar_VariableString("team_headmodel"));
+ if (q3Model) {
+ q3Model = qfalse;
+ updateModel = qtrue;
+ }
+ }
+ if (updateModel) {
+ memset( &info, 0, sizeof(playerInfo_t) );
+ viewangles[YAW] = 180 - 10;
+ viewangles[PITCH] = 0;
+ viewangles[ROLL] = 0;
+ VectorClear( moveangles );
+ UI_PlayerInfo_SetModel( &info, model, head, team);
+ UI_PlayerInfo_SetInfo( &info, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse );
+// UI_RegisterClientModelname( &info, model, head, team);
+ updateModel = qfalse;
+ }
+
+ UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info, uiInfo.uiDC.realTime / 2);
+
+}
+
+static void UI_DrawNetSource(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_netSource.integer < 0 || ui_netSource.integer > numNetSources) {
+ ui_netSource.integer = 0;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, va("Source: %s", netSources[ui_netSource.integer]), 0, 0, textStyle);
+}
+
+static void UI_DrawNetMapPreview(rectDef_t *rect, float scale, vec4_t color) {
+
+ if (uiInfo.serverStatus.currentServerPreview > 0) {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview);
+ } else {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen"));
+ }
+}
+
+static void UI_DrawNetMapCinematic(rectDef_t *rect, float scale, vec4_t color) {
+ if (ui_currentNetMap.integer < 0 || ui_currentNetMap.integer > uiInfo.mapCount) {
+ ui_currentNetMap.integer = 0;
+ trap_Cvar_Set("ui_currentNetMap", "0");
+ }
+
+ if (uiInfo.serverStatus.currentServerCinematic >= 0) {
+ trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic);
+ trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic);
+ } else {
+ UI_DrawNetMapPreview(rect, scale, color);
+ }
+}
+
+
+
+static void UI_DrawNetFilter(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) {
+ ui_serverFilterType.integer = 0;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, va("Filter: %s", serverFilters[ui_serverFilterType.integer].description), 0, 0, textStyle);
+}
+
+
+static void UI_DrawTier(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, va("Tier: %s", uiInfo.tierList[i].tierName),0, 0, textStyle);
+}
+
+static void UI_DrawTierMap(rectDef_t *rect, int index) {
+ int i;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+
+ if (uiInfo.tierList[i].mapHandles[index] == -1) {
+ uiInfo.tierList[i].mapHandles[index] = trap_R_RegisterShaderNoMip(va("levelshots/%s", uiInfo.tierList[i].maps[index]));
+ }
+
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.tierList[i].mapHandles[index]);
+}
+
+static const char *UI_EnglishMapName(const char *map) {
+ int i;
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ if (Q_stricmp(map, uiInfo.mapList[i].mapLoadName) == 0) {
+ return uiInfo.mapList[i].mapName;
+ }
+ }
+ return "";
+}
+
+static void UI_DrawTierMapName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i, j;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+ j = trap_Cvar_VariableValue("ui_currentMap");
+ if (j < 0 || j > MAPS_PER_TIER) {
+ j = 0;
+ }
+
+ Text_Paint(rect->x, rect->y, scale, color, UI_EnglishMapName(uiInfo.tierList[i].maps[j]), 0, 0, textStyle);
+}
+
+static void UI_DrawTierGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i, j;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+ j = trap_Cvar_VariableValue("ui_currentMap");
+ if (j < 0 || j > MAPS_PER_TIER) {
+ j = 0;
+ }
+
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[uiInfo.tierList[i].gameTypes[j]].gameType , 0, 0, textStyle);
+}
+
+
+static const char *UI_AIFromName(const char *name) {
+ int j;
+ for (j = 0; j < uiInfo.aliasCount; j++) {
+ if (Q_stricmp(uiInfo.aliasList[j].name, name) == 0) {
+ return uiInfo.aliasList[j].ai;
+ }
+ }
+ return "James";
+}
+
+static qboolean updateOpponentModel = qtrue;
+static void UI_DrawOpponent(rectDef_t *rect) {
+ static playerInfo_t info2;
+ char model[MAX_QPATH];
+ char headmodel[MAX_QPATH];
+ char team[256];
+ vec3_t viewangles;
+ vec3_t moveangles;
+
+ if (updateOpponentModel) {
+
+ strcpy(model, UI_Cvar_VariableString("ui_opponentModel"));
+ strcpy(headmodel, UI_Cvar_VariableString("ui_opponentModel"));
+ team[0] = '\0';
+
+ memset( &info2, 0, sizeof(playerInfo_t) );
+ viewangles[YAW] = 180 - 10;
+ viewangles[PITCH] = 0;
+ viewangles[ROLL] = 0;
+ VectorClear( moveangles );
+ UI_PlayerInfo_SetModel( &info2, model, headmodel, "");
+ UI_PlayerInfo_SetInfo( &info2, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse );
+ UI_RegisterClientModelname( &info2, model, headmodel, team);
+ updateOpponentModel = qfalse;
+ }
+
+ UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info2, uiInfo.uiDC.realTime / 2);
+
+}
+
+static void UI_NextOpponent( void ) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ i++;
+ if (i >= uiInfo.teamCount) {
+ i = 0;
+ }
+ if (i == j) {
+ i++;
+ if ( i >= uiInfo.teamCount) {
+ i = 0;
+ }
+ }
+ trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName );
+}
+
+static void UI_PriorOpponent( void ) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ i--;
+ if (i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+ if (i == j) {
+ i--;
+ if ( i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+ }
+ trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName );
+}
+
+static void UI_DrawPlayerLogo(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawPlayerLogoMetal(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawPlayerLogoName(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawOpponentLogo(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawOpponentLogoMetal(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawOpponentLogoName(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawAllMapsSelection(rectDef_t *rect, float scale, vec4_t color, int textStyle, qboolean net) {
+ int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (map >= 0 && map < uiInfo.mapCount) {
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawPlayerListSelection( rectDef_t *rect, float scale,
+ vec4_t color, int textStyle )
+{
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ Text_Paint(rect->x, rect->y, scale, color,
+ uiInfo.rawPlayerNames[ uiInfo.playerIndex ],
+ 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawTeamListSelection( rectDef_t *rect, float scale,
+ vec4_t color, int textStyle )
+{
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ Text_Paint(rect->x, rect->y, scale, color,
+ uiInfo.rawTeamNames[ uiInfo.teamIndex ],
+ 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawOpponentName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_opponentName"), 0, 0, textStyle);
+}
+
+
+static int UI_OwnerDrawWidth(int ownerDraw, float scale) {
+ int i, h, value;
+ const char *text;
+ const char *s = NULL;
+
+ switch( ownerDraw )
+ {
+ case UI_HANDICAP:
+ h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") );
+ i = 20 - h / 5;
+ s = handicapValues[i];
+ break;
+ case UI_CLANNAME:
+ s = UI_Cvar_VariableString("ui_teamName");
+ break;
+ case UI_GAMETYPE:
+ s = uiInfo.gameTypes[ui_gameType.integer].gameType;
+ break;
+ case UI_SKILL:
+ i = trap_Cvar_VariableValue( "g_spSkill" );
+ if (i < 1 || i > numSkillLevels) {
+ i = 1;
+ }
+ s = skillLevels[i-1];
+ break;
+ case UI_BLUETEAMNAME:
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_blueTeam"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ s = va("%s: %s", "Blue", uiInfo.teamList[i].teamName);
+ }
+ break;
+ case UI_REDTEAMNAME:
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_redTeam"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ s = va("%s: %s", "Red", uiInfo.teamList[i].teamName);
+ }
+ break;
+ case UI_BLUETEAM1:
+ case UI_BLUETEAM2:
+ case UI_BLUETEAM3:
+ case UI_BLUETEAM4:
+ case UI_BLUETEAM5:
+ value = trap_Cvar_VariableValue(va("ui_blueteam%i", ownerDraw-UI_BLUETEAM1 + 1));
+ if (value <= 0) {
+ text = "Closed";
+ } else if (value == 1) {
+ text = "Human";
+ } else {
+ value -= 2;
+ if (value >= uiInfo.aliasCount) {
+ value = 0;
+ }
+ text = uiInfo.aliasList[value].name;
+ }
+ s = va("%i. %s", ownerDraw-UI_BLUETEAM1 + 1, text);
+ break;
+ case UI_REDTEAM1:
+ case UI_REDTEAM2:
+ case UI_REDTEAM3:
+ case UI_REDTEAM4:
+ case UI_REDTEAM5:
+ value = trap_Cvar_VariableValue(va("ui_redteam%i", ownerDraw-UI_REDTEAM1 + 1));
+ if (value <= 0) {
+ text = "Closed";
+ } else if (value == 1) {
+ text = "Human";
+ } else {
+ value -= 2;
+ if (value >= uiInfo.aliasCount) {
+ value = 0;
+ }
+ text = uiInfo.aliasList[value].name;
+ }
+ s = va("%i. %s", ownerDraw-UI_REDTEAM1 + 1, text);
+ break;
+ case UI_NETSOURCE:
+ if (ui_netSource.integer < 0 || ui_netSource.integer > uiInfo.numJoinGameTypes) {
+ ui_netSource.integer = 0;
+ }
+ s = va("Source: %s", netSources[ui_netSource.integer]);
+ break;
+ case UI_NETFILTER:
+ if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) {
+ ui_serverFilterType.integer = 0;
+ }
+ s = va("Filter: %s", serverFilters[ui_serverFilterType.integer].description );
+ break;
+ case UI_TIER:
+ break;
+ case UI_TIER_MAPNAME:
+ break;
+ case UI_TIER_GAMETYPE:
+ break;
+ case UI_ALLMAPS_SELECTION:
+ break;
+ case UI_PLAYERLIST_SELECTION:
+ break;
+ case UI_TEAMLIST_SELECTION:
+ break;
+ case UI_OPPONENT_NAME:
+ break;
+ case UI_KEYBINDSTATUS:
+ if (Display_KeyBindPending()) {
+ s = "Waiting for new key... Press ESCAPE to cancel";
+ } else {
+ s = "Press ENTER or CLICK to change, Press BACKSPACE to clear";
+ }
+ break;
+ case UI_SERVERREFRESHDATE:
+ s = UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer));
+ break;
+ default:
+ break;
+ }
+
+ if (s) {
+ return Text_Width(s, scale, 0);
+ }
+ return 0;
+}
+
+static void UI_DrawBotName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int value = uiInfo.botIndex;
+ const char *text = "";
+
+ if( value >= UI_GetNumBots( ) )
+ value = 0;
+
+ text = UI_GetBotNameByNumber( value );
+
+ Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle);
+}
+
+static void UI_DrawBotSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (uiInfo.skillIndex >= 0 && uiInfo.skillIndex < numSkillLevels) {
+ Text_Paint(rect->x, rect->y, scale, color, skillLevels[uiInfo.skillIndex], 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawRedBlue(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, (uiInfo.redBlue == 0) ? "Red" : "Blue", 0, 0, textStyle);
+}
+
+/*
+===============
+UI_BuildPlayerList
+===============
+*/
+static void UI_BuildPlayerList( void ) {
+ uiClientState_t cs;
+ int n, count, team, team2, playerTeamNumber;
+ char info[MAX_INFO_STRING];
+
+ trap_GetClientState( &cs );
+ trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING );
+ uiInfo.playerNumber = cs.clientNum;
+ uiInfo.teamLeader = atoi(Info_ValueForKey(info, "tl"));
+ team = atoi(Info_ValueForKey(info, "t"));
+ trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) );
+ count = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
+ uiInfo.playerCount = 0;
+ uiInfo.myTeamCount = 0;
+ uiInfo.myPlayerIndex = 0;
+ playerTeamNumber = 0;
+ for( n = 0; n < count; n++ ) {
+ trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING );
+
+ if (info[0]) {
+ BG_ClientListParse( &uiInfo.ignoreList[ uiInfo.playerCount ],
+ Info_ValueForKey( info, "ig" ) );
+ Q_strncpyz( uiInfo.rawPlayerNames[uiInfo.playerCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] );
+ uiInfo.clientNums[uiInfo.playerCount] = n;
+ if( n == uiInfo.playerNumber )
+ uiInfo.myPlayerIndex = uiInfo.playerCount;
+ uiInfo.playerCount++;
+ team2 = atoi(Info_ValueForKey(info, "t"));
+ if (team2 == team) {
+ Q_strncpyz( uiInfo.rawTeamNames[uiInfo.myTeamCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] );
+ uiInfo.teamClientNums[uiInfo.myTeamCount] = n;
+ if (uiInfo.playerNumber == n) {
+ playerTeamNumber = uiInfo.myTeamCount;
+ }
+ uiInfo.myTeamCount++;
+ }
+ }
+ }
+
+ if (!uiInfo.teamLeader) {
+ trap_Cvar_Set("cg_selectedPlayer", va("%d", playerTeamNumber));
+ }
+
+ n = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (n < 0 || n > uiInfo.myTeamCount) {
+ n = 0;
+ }
+ if (n < uiInfo.myTeamCount) {
+ trap_Cvar_Set("cg_selectedPlayerName", uiInfo.teamNames[n]);
+ }
+}
+
+
+static void UI_DrawSelectedPlayer(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ char name[ MAX_NAME_LENGTH ];
+ char *s;
+
+ if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+ if( uiInfo.teamLeader )
+ s = UI_Cvar_VariableString("cg_selectedPlayerName");
+ else
+ s = UI_Cvar_VariableString("name");
+ Q_strncpyz( name, s, sizeof( name ) );
+ Text_Paint(rect->x, rect->y, scale, color, name, 0, 0, textStyle);
+}
+
+static void UI_DrawServerRefreshDate(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (uiInfo.serverStatus.refreshActive) {
+ vec4_t lowLight, newColor;
+ lowLight[0] = 0.8 * color[0];
+ lowLight[1] = 0.8 * color[1];
+ lowLight[2] = 0.8 * color[2];
+ lowLight[3] = 0.8 * color[3];
+ LerpColor(color,lowLight,newColor,0.5+0.5*sin(uiInfo.uiDC.realTime / PULSE_DIVISOR));
+ Text_Paint(rect->x, rect->y, scale, newColor, va("Getting info for %d servers (ESC to cancel)", trap_LAN_GetServerCount(ui_netSource.integer)), 0, 0, textStyle);
+ } else {
+ char buff[64];
+ Q_strncpyz(buff, UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)), 64);
+ Text_Paint(rect->x, rect->y, scale, color, va("Refresh Time: %s", buff), 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawServerMOTD(rectDef_t *rect, float scale, vec4_t color) {
+ if (uiInfo.serverStatus.motdLen) {
+ float maxX;
+
+ if (uiInfo.serverStatus.motdWidth == -1) {
+ uiInfo.serverStatus.motdWidth = 0;
+ uiInfo.serverStatus.motdPaintX = rect->x + 1;
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+
+ if (uiInfo.serverStatus.motdOffset > uiInfo.serverStatus.motdLen) {
+ uiInfo.serverStatus.motdOffset = 0;
+ uiInfo.serverStatus.motdPaintX = rect->x + 1;
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+
+ if (uiInfo.uiDC.realTime > uiInfo.serverStatus.motdTime) {
+ uiInfo.serverStatus.motdTime = uiInfo.uiDC.realTime + 10;
+ if (uiInfo.serverStatus.motdPaintX <= rect->x + 2) {
+ if (uiInfo.serverStatus.motdOffset < uiInfo.serverStatus.motdLen) {
+ uiInfo.serverStatus.motdPaintX += Text_Width(&uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], scale, 1) - 1;
+ uiInfo.serverStatus.motdOffset++;
+ } else {
+ uiInfo.serverStatus.motdOffset = 0;
+ if (uiInfo.serverStatus.motdPaintX2 >= 0) {
+ uiInfo.serverStatus.motdPaintX = uiInfo.serverStatus.motdPaintX2;
+ } else {
+ uiInfo.serverStatus.motdPaintX = rect->x + rect->w - 2;
+ }
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+ } else {
+ //serverStatus.motdPaintX--;
+ uiInfo.serverStatus.motdPaintX -= 2;
+ if (uiInfo.serverStatus.motdPaintX2 >= 0) {
+ //serverStatus.motdPaintX2--;
+ uiInfo.serverStatus.motdPaintX2 -= 2;
+ }
+ }
+ }
+
+ maxX = rect->x + rect->w - 2;
+ Text_Paint_Limit(&maxX, uiInfo.serverStatus.motdPaintX, rect->y + rect->h - 3, scale, color, &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], 0, 0);
+ if (uiInfo.serverStatus.motdPaintX2 >= 0) {
+ float maxX2 = rect->x + rect->w - 2;
+ Text_Paint_Limit(&maxX2, uiInfo.serverStatus.motdPaintX2, rect->y + rect->h - 3, scale, color, uiInfo.serverStatus.motd, 0, uiInfo.serverStatus.motdOffset);
+ }
+ if (uiInfo.serverStatus.motdOffset && maxX > 0) {
+ // if we have an offset ( we are skipping the first part of the string ) and we fit the string
+ if (uiInfo.serverStatus.motdPaintX2 == -1) {
+ uiInfo.serverStatus.motdPaintX2 = rect->x + rect->w - 2;
+ }
+ } else {
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+
+ }
+}
+
+static void UI_DrawKeyBindStatus(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+// int ofs = 0; TTimo: unused
+ if (Display_KeyBindPending()) {
+ Text_Paint(rect->x, rect->y, scale, color, "Waiting for new key... Press ESCAPE to cancel", 0, 0, textStyle);
+ } else {
+ Text_Paint(rect->x, rect->y, scale, color, "Press ENTER or CLICK to change, Press BACKSPACE to clear", 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawGLInfo(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ char * eptr;
+ char buff[1024];
+ const char *lines[64];
+ int y, numLines, i;
+
+ Text_Paint(rect->x + 2, rect->y, scale, color, va("VENDOR: %s", uiInfo.uiDC.glconfig.vendor_string), 0, 30, textStyle);
+ Text_Paint(rect->x + 2, rect->y + 15, scale, color, va("VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string), 0, 30, textStyle);
+ Text_Paint(rect->x + 2, rect->y + 30, scale, color, va ("PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)", uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits), 0, 30, textStyle);
+
+ // build null terminated extension strings
+ // TTimo: https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=399
+ // in TA this was not directly crashing, but displaying a nasty broken shader right in the middle
+ // brought down the string size to 1024, there's not much that can be shown on the screen anyway
+ Q_strncpyz(buff, uiInfo.uiDC.glconfig.extensions_string, 1024);
+ eptr = buff;
+ y = rect->y + 45;
+ numLines = 0;
+ while ( y < rect->y + rect->h && *eptr )
+ {
+ while ( *eptr && *eptr == ' ' )
+ *eptr++ = '\0';
+
+ // track start of valid string
+ if (*eptr && *eptr != ' ') {
+ lines[numLines++] = eptr;
+ }
+
+ while ( *eptr && *eptr != ' ' )
+ eptr++;
+ }
+
+ i = 0;
+ while (i < numLines) {
+ Text_Paint(rect->x + 2, y, scale, color, lines[i++], 0, 20, textStyle);
+ if (i < numLines) {
+ Text_Paint(rect->x + rect->w / 2, y, scale, color, lines[i++], 0, 20, textStyle);
+ }
+ y += 10;
+ if (y > rect->y + rect->h - 11) {
+ break;
+ }
+ }
+
+
+}
+
+// FIXME: table drive
+//
+static void UI_OwnerDraw( float x, float y, float w, float h,
+ float text_x, float text_y, int ownerDraw,
+ int ownerDrawFlags, int align, float special,
+ float scale, vec4_t color, qhandle_t shader, int textStyle )
+{
+ rectDef_t rect;
+ tremInfoPane_t *pane = NULL;
+
+ rect.x = x + text_x;
+ rect.y = y + text_y;
+ rect.w = w;
+ rect.h = h;
+
+ switch( ownerDraw )
+ {
+ case UI_TEAMINFOPANE:
+ if( ( pane = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_ACLASSINFOPANE:
+ if( ( pane = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_AUPGRADEINFOPANE:
+ if( ( pane = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HITEMINFOPANE:
+ if( ( pane = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HBUYINFOPANE:
+ if( ( pane = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HSELLINFOPANE:
+ if( ( pane = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_ABUILDINFOPANE:
+ if( ( pane = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HBUILDINFOPANE:
+ if( ( pane = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HANDICAP:
+ UI_DrawHandicap(&rect, scale, color, textStyle);
+ break;
+ case UI_PLAYERMODEL:
+ UI_DrawPlayerModel(&rect);
+ break;
+ case UI_CLANNAME:
+ UI_DrawClanName(&rect, scale, color, textStyle);
+ break;
+ case UI_CLANLOGO:
+ UI_DrawClanLogo(&rect, scale, color);
+ break;
+ case UI_CLANCINEMATIC:
+ UI_DrawClanCinematic(&rect, scale, color);
+ break;
+ case UI_PREVIEWCINEMATIC:
+ UI_DrawPreviewCinematic(&rect, scale, color);
+ break;
+ case UI_GAMETYPE:
+ UI_DrawGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_NETGAMETYPE:
+ UI_DrawNetGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_JOINGAMETYPE:
+ UI_DrawJoinGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_MAPPREVIEW:
+ UI_DrawMapPreview(&rect, scale, color, qtrue);
+ break;
+ case UI_MAP_TIMETOBEAT:
+ UI_DrawMapTimeToBeat(&rect, scale, color, textStyle);
+ break;
+ case UI_MAPCINEMATIC:
+ UI_DrawMapCinematic(&rect, scale, color, qfalse);
+ break;
+ case UI_STARTMAPCINEMATIC:
+ UI_DrawMapCinematic(&rect, scale, color, qtrue);
+ break;
+ case UI_SKILL:
+ UI_DrawSkill(&rect, scale, color, textStyle);
+ break;
+ case UI_BLUETEAMNAME:
+ UI_DrawTeamName(&rect, scale, color, qtrue, textStyle);
+ break;
+ case UI_REDTEAMNAME:
+ UI_DrawTeamName(&rect, scale, color, qfalse, textStyle);
+ break;
+ case UI_BLUETEAM1:
+ case UI_BLUETEAM2:
+ case UI_BLUETEAM3:
+ case UI_BLUETEAM4:
+ case UI_BLUETEAM5:
+ UI_DrawTeamMember(&rect, scale, color, qtrue, ownerDraw - UI_BLUETEAM1 + 1, textStyle);
+ break;
+ case UI_REDTEAM1:
+ case UI_REDTEAM2:
+ case UI_REDTEAM3:
+ case UI_REDTEAM4:
+ case UI_REDTEAM5:
+ UI_DrawTeamMember(&rect, scale, color, qfalse, ownerDraw - UI_REDTEAM1 + 1, textStyle);
+ break;
+ case UI_NETSOURCE:
+ UI_DrawNetSource(&rect, scale, color, textStyle);
+ break;
+ case UI_NETMAPPREVIEW:
+ UI_DrawNetMapPreview(&rect, scale, color);
+ break;
+ case UI_NETMAPCINEMATIC:
+ UI_DrawNetMapCinematic(&rect, scale, color);
+ break;
+ case UI_NETFILTER:
+ UI_DrawNetFilter(&rect, scale, color, textStyle);
+ break;
+ case UI_TIER:
+ UI_DrawTier(&rect, scale, color, textStyle);
+ break;
+ case UI_OPPONENTMODEL:
+ UI_DrawOpponent(&rect);
+ break;
+ case UI_TIERMAP1:
+ UI_DrawTierMap(&rect, 0);
+ break;
+ case UI_TIERMAP2:
+ UI_DrawTierMap(&rect, 1);
+ break;
+ case UI_TIERMAP3:
+ UI_DrawTierMap(&rect, 2);
+ break;
+ case UI_PLAYERLOGO:
+ UI_DrawPlayerLogo(&rect, color);
+ break;
+ case UI_PLAYERLOGO_METAL:
+ UI_DrawPlayerLogoMetal(&rect, color);
+ break;
+ case UI_PLAYERLOGO_NAME:
+ UI_DrawPlayerLogoName(&rect, color);
+ break;
+ case UI_OPPONENTLOGO:
+ UI_DrawOpponentLogo(&rect, color);
+ break;
+ case UI_OPPONENTLOGO_METAL:
+ UI_DrawOpponentLogoMetal(&rect, color);
+ break;
+ case UI_OPPONENTLOGO_NAME:
+ UI_DrawOpponentLogoName(&rect, color);
+ break;
+ case UI_TIER_MAPNAME:
+ UI_DrawTierMapName(&rect, scale, color, textStyle);
+ break;
+ case UI_TIER_GAMETYPE:
+ UI_DrawTierGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_ALLMAPS_SELECTION:
+ UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qtrue);
+ break;
+ case UI_MAPS_SELECTION:
+ UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qfalse);
+ break;
+ case UI_PLAYERLIST_SELECTION:
+ UI_DrawPlayerListSelection(&rect, scale, color, textStyle);
+ break;
+ case UI_TEAMLIST_SELECTION:
+ UI_DrawTeamListSelection(&rect, scale, color, textStyle);
+ break;
+ case UI_OPPONENT_NAME:
+ UI_DrawOpponentName(&rect, scale, color, textStyle);
+ break;
+ case UI_BOTNAME:
+ UI_DrawBotName(&rect, scale, color, textStyle);
+ break;
+ case UI_BOTSKILL:
+ UI_DrawBotSkill(&rect, scale, color, textStyle);
+ break;
+ case UI_REDBLUE:
+ UI_DrawRedBlue(&rect, scale, color, textStyle);
+ break;
+ case UI_SELECTEDPLAYER:
+ UI_DrawSelectedPlayer(&rect, scale, color, textStyle);
+ break;
+ case UI_SERVERREFRESHDATE:
+ UI_DrawServerRefreshDate(&rect, scale, color, textStyle);
+ break;
+ case UI_SERVERMOTD:
+ UI_DrawServerMOTD(&rect, scale, color);
+ break;
+ case UI_GLINFO:
+ UI_DrawGLInfo(&rect,scale, color, textStyle);
+ break;
+ case UI_KEYBINDSTATUS:
+ UI_DrawKeyBindStatus(&rect,scale, color, textStyle);
+ break;
+ default:
+ break;
+ }
+
+}
+
+static qboolean UI_OwnerDrawVisible(int flags) {
+ qboolean vis = qtrue;
+ uiClientState_t cs;
+ pTeam_t team;
+ char info[ MAX_INFO_STRING ];
+
+ trap_GetClientState( &cs );
+ trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING );
+ team = atoi( Info_ValueForKey( info, "t" ) );
+
+
+ while (flags) {
+
+ if( flags & UI_SHOW_NOTSPECTATING )
+ {
+ if( team == PTE_NONE )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_NOTSPECTATING;
+ }
+
+ if( flags & UI_SHOW_VOTEACTIVE )
+ {
+ if( !trap_Cvar_VariableValue( "ui_voteActive" ) )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_VOTEACTIVE;
+ }
+
+ if( flags & UI_SHOW_CANVOTE )
+ {
+ if( trap_Cvar_VariableValue( "ui_voteActive" ) )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_CANVOTE;
+ }
+
+ if( flags & UI_SHOW_TEAMVOTEACTIVE )
+ {
+ if( team == PTE_ALIENS )
+ {
+ if( !trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) )
+ vis = qfalse;
+ }
+ else if( team == PTE_HUMANS )
+ {
+ if( !trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) )
+ vis = qfalse;
+ }
+
+ flags &= ~UI_SHOW_TEAMVOTEACTIVE;
+ }
+
+ if( flags & UI_SHOW_CANTEAMVOTE )
+ {
+ if( team == PTE_ALIENS )
+ {
+ if( trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) )
+ vis = qfalse;
+ }
+ else if( team == PTE_HUMANS )
+ {
+ if( trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) )
+ vis = qfalse;
+ }
+
+ flags &= ~UI_SHOW_CANTEAMVOTE;
+ }
+
+ if (flags & UI_SHOW_LEADER) {
+ // these need to show when this client can give orders to a player or a group
+ if (!uiInfo.teamLeader) {
+ vis = qfalse;
+ } else {
+ // if showing yourself
+ if (ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber) {
+ vis = qfalse;
+ }
+ }
+ flags &= ~UI_SHOW_LEADER;
+ }
+ if (flags & UI_SHOW_NOTLEADER) {
+ // these need to show when this client is assigning their own status or they are NOT the leader
+ if (uiInfo.teamLeader) {
+ // if not showing yourself
+ if (!(ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber)) {
+ vis = qfalse;
+ }
+ // these need to show when this client can give orders to a player or a group
+ }
+ flags &= ~UI_SHOW_NOTLEADER;
+ }
+ if (flags & UI_SHOW_FAVORITESERVERS) {
+ // this assumes you only put this type of display flag on something showing in the proper context
+ if (ui_netSource.integer != AS_FAVORITES) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_FAVORITESERVERS;
+ }
+ if (flags & UI_SHOW_NOTFAVORITESERVERS) {
+ // this assumes you only put this type of display flag on something showing in the proper context
+ if (ui_netSource.integer == AS_FAVORITES) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_NOTFAVORITESERVERS;
+ }
+ if (flags & UI_SHOW_NEWHIGHSCORE) {
+ if (uiInfo.newHighScoreTime < uiInfo.uiDC.realTime) {
+ vis = qfalse;
+ } else {
+ if (uiInfo.soundHighScore) {
+ if (trap_Cvar_VariableValue("sv_killserver") == 0) {
+ // wait on server to go down before playing sound
+ trap_S_StartLocalSound(uiInfo.newHighScoreSound, CHAN_ANNOUNCER);
+ uiInfo.soundHighScore = qfalse;
+ }
+ }
+ }
+ flags &= ~UI_SHOW_NEWHIGHSCORE;
+ }
+ if (flags & UI_SHOW_NEWBESTTIME) {
+ if (uiInfo.newBestTime < uiInfo.uiDC.realTime) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_NEWBESTTIME;
+ }
+ if (flags & UI_SHOW_DEMOAVAILABLE) {
+ if (!uiInfo.demoAvailable) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_DEMOAVAILABLE;
+ } else {
+ flags = 0;
+ }
+ }
+ return vis;
+}
+
+static qboolean UI_Handicap_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int h;
+ h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") );
+ if (key == K_MOUSE2) {
+ h -= 5;
+ } else {
+ h += 5;
+ }
+ if (h > 100) {
+ h = 5;
+ } else if (h < 0) {
+ h = 100;
+ }
+ trap_Cvar_Set( "handicap", va( "%i", h) );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_ClanName_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (uiInfo.teamList[i].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic);
+ uiInfo.teamList[i].cinematic = -1;
+ }
+ if (key == K_MOUSE2) {
+ i--;
+ } else {
+ i++;
+ }
+ if (i >= uiInfo.teamCount) {
+ i = 0;
+ } else if (i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+ trap_Cvar_Set( "ui_teamName", uiInfo.teamList[i].teamName);
+ UI_HeadCountByTeam();
+ UI_FeederSelection(FEEDER_HEADS, 0);
+ updateModel = qtrue;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_GameType_HandleKey(int flags, float *special, int key, qboolean resetMap) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int oldCount = UI_MapCountByGameType(qtrue);
+
+ // hard coded mess here
+ if (key == K_MOUSE2) {
+ ui_gameType.integer--;
+ if (ui_gameType.integer == 2) {
+ ui_gameType.integer = 1;
+ } else if (ui_gameType.integer < 2) {
+ ui_gameType.integer = uiInfo.numGameTypes - 1;
+ }
+ } else {
+ ui_gameType.integer++;
+ if (ui_gameType.integer >= uiInfo.numGameTypes) {
+ ui_gameType.integer = 1;
+ } else if (ui_gameType.integer == 2) {
+ ui_gameType.integer = 3;
+ }
+ }
+
+ trap_Cvar_Set("ui_Q3Model", "0");
+
+ trap_Cvar_Set("ui_gameType", va("%d", ui_gameType.integer));
+ UI_SetCapFragLimits(qtrue);
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);
+ if (resetMap && oldCount != UI_MapCountByGameType(qtrue)) {
+ trap_Cvar_Set( "ui_currentMap", "0");
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, NULL);
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_NetGameType_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_netGameType.integer--;
+ } else {
+ ui_netGameType.integer++;
+ }
+
+ if (ui_netGameType.integer < 0) {
+ ui_netGameType.integer = uiInfo.numGameTypes - 1;
+ } else if (ui_netGameType.integer >= uiInfo.numGameTypes) {
+ ui_netGameType.integer = 0;
+ }
+
+ trap_Cvar_Set( "ui_netGameType", va("%d", ui_netGameType.integer));
+ trap_Cvar_Set( "ui_actualnetGameType", va("%d", uiInfo.gameTypes[ui_netGameType.integer].gtEnum));
+ trap_Cvar_Set( "ui_currentNetMap", "0");
+ UI_MapCountByGameType(qfalse);
+ Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, NULL);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_JoinGameType_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_joinGameType.integer--;
+ } else {
+ ui_joinGameType.integer++;
+ }
+
+ if (ui_joinGameType.integer < 0) {
+ ui_joinGameType.integer = uiInfo.numJoinGameTypes - 1;
+ } else if (ui_joinGameType.integer >= uiInfo.numJoinGameTypes) {
+ ui_joinGameType.integer = 0;
+ }
+
+ trap_Cvar_Set( "ui_joinGameType", va("%d", ui_joinGameType.integer));
+ UI_BuildServerDisplayList(qtrue);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+
+static qboolean UI_Skill_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int i = trap_Cvar_VariableValue( "g_spSkill" );
+
+ if (key == K_MOUSE2) {
+ i--;
+ } else {
+ i++;
+ }
+
+ if (i < 1) {
+ i = numSkillLevels;
+ } else if (i > numSkillLevels) {
+ i = 1;
+ }
+
+ trap_Cvar_Set("g_spSkill", va("%i", i));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_TeamName_HandleKey(int flags, float *special, int key, qboolean blue) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam"));
+
+ if (key == K_MOUSE2) {
+ i--;
+ } else {
+ i++;
+ }
+
+ if (i >= uiInfo.teamCount) {
+ i = 0;
+ } else if (i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+
+ trap_Cvar_Set( (blue) ? "ui_blueTeam" : "ui_redTeam", uiInfo.teamList[i].teamName);
+
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_TeamMember_HandleKey(int flags, float *special, int key, qboolean blue, int num) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ // 0 - None
+ // 1 - Human
+ // 2..NumCharacters - Bot
+ char *cvar = va(blue ? "ui_blueteam%i" : "ui_redteam%i", num);
+ int value = trap_Cvar_VariableValue(cvar);
+
+ if (key == K_MOUSE2) {
+ value--;
+ } else {
+ value++;
+ }
+
+ if( value >= UI_GetNumBots( ) + 2 )
+ value = 0;
+ else if( value < 0 )
+ value = UI_GetNumBots( ) + 2 - 1;
+
+ trap_Cvar_Set(cvar, va("%i", value));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_NetSource_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_netSource.integer--;
+ if (ui_netSource.integer == AS_MPLAYER)
+ ui_netSource.integer--;
+ } else {
+ ui_netSource.integer++;
+ if (ui_netSource.integer == AS_MPLAYER)
+ ui_netSource.integer++;
+ }
+
+ if (ui_netSource.integer >= numNetSources) {
+ ui_netSource.integer = 0;
+ } else if (ui_netSource.integer < 0) {
+ ui_netSource.integer = numNetSources - 1;
+ }
+
+ UI_BuildServerDisplayList(qtrue);
+ if (ui_netSource.integer != AS_GLOBAL) {
+ UI_StartServerRefresh(qtrue);
+ }
+ trap_Cvar_Set( "ui_netSource", va("%d", ui_netSource.integer));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_NetFilter_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_serverFilterType.integer--;
+ } else {
+ ui_serverFilterType.integer++;
+ }
+
+ if (ui_serverFilterType.integer >= numServerFilters) {
+ ui_serverFilterType.integer = 0;
+ } else if (ui_serverFilterType.integer < 0) {
+ ui_serverFilterType.integer = numServerFilters - 1;
+ }
+ UI_BuildServerDisplayList(qtrue);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_OpponentName_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ if (key == K_MOUSE2) {
+ UI_PriorOpponent();
+ } else {
+ UI_NextOpponent();
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_BotName_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int value = uiInfo.botIndex;
+
+ if (key == K_MOUSE2) {
+ value--;
+ } else {
+ value++;
+ }
+
+
+ if( value >= UI_GetNumBots( ) + 2 )
+ value = 0;
+ else if( value < 0 )
+ value = UI_GetNumBots( ) + 2 - 1;
+
+ uiInfo.botIndex = value;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_BotSkill_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ if (key == K_MOUSE2) {
+ uiInfo.skillIndex--;
+ } else {
+ uiInfo.skillIndex++;
+ }
+ if (uiInfo.skillIndex >= numSkillLevels) {
+ uiInfo.skillIndex = 0;
+ } else if (uiInfo.skillIndex < 0) {
+ uiInfo.skillIndex = numSkillLevels-1;
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_RedBlue_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ uiInfo.redBlue ^= 1;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+
+static qboolean UI_SelectedPlayer_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int selected;
+
+ UI_BuildPlayerList();
+ if (!uiInfo.teamLeader) {
+ return qfalse;
+ }
+ selected = trap_Cvar_VariableValue("cg_selectedPlayer");
+
+ if (key == K_MOUSE2) {
+ selected--;
+ } else {
+ selected++;
+ }
+
+ if (selected > uiInfo.myTeamCount) {
+ selected = 0;
+ } else if (selected < 0) {
+ selected = uiInfo.myTeamCount;
+ }
+
+ if (selected == uiInfo.myTeamCount) {
+ trap_Cvar_Set( "cg_selectedPlayerName", "Everyone");
+ } else {
+ trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[selected]);
+ }
+ trap_Cvar_Set( "cg_selectedPlayer", va("%d", selected));
+ }
+ return qfalse;
+}
+
+
+static qboolean UI_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) {
+ switch (ownerDraw) {
+ case UI_HANDICAP:
+ return UI_Handicap_HandleKey(flags, special, key);
+ break;
+ case UI_CLANNAME:
+ return UI_ClanName_HandleKey(flags, special, key);
+ break;
+ case UI_GAMETYPE:
+ return UI_GameType_HandleKey(flags, special, key, qtrue);
+ break;
+ case UI_NETGAMETYPE:
+ return UI_NetGameType_HandleKey(flags, special, key);
+ break;
+ case UI_JOINGAMETYPE:
+ return UI_JoinGameType_HandleKey(flags, special, key);
+ break;
+ case UI_SKILL:
+ return UI_Skill_HandleKey(flags, special, key);
+ break;
+ case UI_BLUETEAMNAME:
+ return UI_TeamName_HandleKey(flags, special, key, qtrue);
+ break;
+ case UI_REDTEAMNAME:
+ return UI_TeamName_HandleKey(flags, special, key, qfalse);
+ break;
+ case UI_BLUETEAM1:
+ case UI_BLUETEAM2:
+ case UI_BLUETEAM3:
+ case UI_BLUETEAM4:
+ case UI_BLUETEAM5:
+ UI_TeamMember_HandleKey(flags, special, key, qtrue, ownerDraw - UI_BLUETEAM1 + 1);
+ break;
+ case UI_REDTEAM1:
+ case UI_REDTEAM2:
+ case UI_REDTEAM3:
+ case UI_REDTEAM4:
+ case UI_REDTEAM5:
+ UI_TeamMember_HandleKey(flags, special, key, qfalse, ownerDraw - UI_REDTEAM1 + 1);
+ break;
+ case UI_NETSOURCE:
+ UI_NetSource_HandleKey(flags, special, key);
+ break;
+ case UI_NETFILTER:
+ UI_NetFilter_HandleKey(flags, special, key);
+ break;
+ case UI_OPPONENT_NAME:
+ UI_OpponentName_HandleKey(flags, special, key);
+ break;
+ case UI_BOTNAME:
+ return UI_BotName_HandleKey(flags, special, key);
+ break;
+ case UI_BOTSKILL:
+ return UI_BotSkill_HandleKey(flags, special, key);
+ break;
+ case UI_REDBLUE:
+ UI_RedBlue_HandleKey(flags, special, key);
+ break;
+ case UI_SELECTEDPLAYER:
+ UI_SelectedPlayer_HandleKey(flags, special, key);
+ break;
+ default:
+ break;
+ }
+
+ return qfalse;
+}
+
+
+static float UI_GetValue(int ownerDraw) {
+ return 0;
+}
+
+/*
+=================
+UI_ServersQsortCompare
+=================
+*/
+static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) {
+ return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int*)arg1, *(int*)arg2);
+}
+
+
+/*
+=================
+UI_ServersSort
+=================
+*/
+void UI_ServersSort(int column, qboolean force) {
+
+ if ( !force ) {
+ if ( uiInfo.serverStatus.sortKey == column ) {
+ return;
+ }
+ }
+
+ uiInfo.serverStatus.sortKey = column;
+ qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), UI_ServersQsortCompare);
+}
+
+
+/*
+===============
+UI_GetCurrentAlienStage
+===============
+*/
+static stage_t UI_GetCurrentAlienStage( void )
+{
+ char buffer[ MAX_TOKEN_CHARS ];
+ stage_t stage, dummy;
+
+ trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) );
+ sscanf( buffer, "%d %d", (int *)&stage , (int *)&dummy );
+
+ return stage;
+}
+
+/*
+===============
+UI_GetCurrentHumanStage
+===============
+*/
+static stage_t UI_GetCurrentHumanStage( void )
+{
+ char buffer[ MAX_TOKEN_CHARS ];
+ stage_t stage, dummy;
+
+ trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) );
+ sscanf( buffer, "%d %d", (int *)&dummy, (int *)&stage );
+
+ return stage;
+}
+
+/*
+===============
+UI_LoadTremTeams
+===============
+*/
+static void UI_LoadTremTeams( void )
+{
+ uiInfo.tremTeamCount = 4;
+
+ uiInfo.tremTeamList[ 0 ].text = String_Alloc( "Aliens" );
+ uiInfo.tremTeamList[ 0 ].cmd = String_Alloc( "cmd team aliens\n" );
+ uiInfo.tremTeamList[ 0 ].infopane = UI_FindInfoPaneByName( "alienteam" );
+
+ uiInfo.tremTeamList[ 1 ].text = String_Alloc( "Humans" );
+ uiInfo.tremTeamList[ 1 ].cmd = String_Alloc( "cmd team humans\n" );
+ uiInfo.tremTeamList[ 1 ].infopane = UI_FindInfoPaneByName( "humanteam" );
+
+ uiInfo.tremTeamList[ 2 ].text = String_Alloc( "Spectate" );
+ uiInfo.tremTeamList[ 2 ].cmd = String_Alloc( "cmd team spectate\n" );
+ uiInfo.tremTeamList[ 2 ].infopane = UI_FindInfoPaneByName( "spectateteam" );
+
+ uiInfo.tremTeamList[ 3 ].text = String_Alloc( "Auto select" );
+ uiInfo.tremTeamList[ 3 ].cmd = String_Alloc( "cmd team auto\n" );
+ uiInfo.tremTeamList[ 3 ].infopane = UI_FindInfoPaneByName( "autoteam" );
+}
+
+/*
+===============
+UI_AddClass
+===============
+*/
+static void UI_AddClass( pClass_t class )
+{
+ uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].text =
+ String_Alloc( BG_FindHumanNameForClassNum( class ) );
+ uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( class ) ) );
+ uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].infopane =
+ UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( class ) ) );
+
+ uiInfo.tremAlienClassCount++;
+}
+
+/*
+===============
+UI_LoadTremAlienClasses
+===============
+*/
+static void UI_LoadTremAlienClasses( void )
+{
+ uiInfo.tremAlienClassCount = 0;
+
+ if( BG_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) )
+ UI_AddClass( PCL_ALIEN_LEVEL0 );
+
+ if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) &&
+ BG_FindStagesForClass( PCL_ALIEN_BUILDER0_UPG, UI_GetCurrentAlienStage( ) ) )
+ UI_AddClass( PCL_ALIEN_BUILDER0_UPG );
+ else if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) )
+ UI_AddClass( PCL_ALIEN_BUILDER0 );
+}
+
+/*
+===============
+UI_AddItem
+===============
+*/
+static void UI_AddItem( weapon_t weapon )
+{
+ uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].text =
+ String_Alloc( BG_FindHumanNameForWeapon( weapon ) );
+ uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_FindNameForWeapon( weapon ) ) );
+ uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( weapon ) ) );
+
+ uiInfo.tremHumanItemCount++;
+}
+
+/*
+===============
+UI_LoadTremHumanItems
+===============
+*/
+static void UI_LoadTremHumanItems( void )
+{
+ uiInfo.tremHumanItemCount = 0;
+
+ if( BG_WeaponIsAllowed( WP_MACHINEGUN ) )
+ UI_AddItem( WP_MACHINEGUN );
+
+ if( BG_WeaponIsAllowed( WP_HBUILD2 ) &&
+ BG_FindStagesForWeapon( WP_HBUILD2, UI_GetCurrentHumanStage( ) ) )
+ UI_AddItem( WP_HBUILD2 );
+ else if( BG_WeaponIsAllowed( WP_HBUILD ) )
+ UI_AddItem( WP_HBUILD );
+}
+
+/*
+===============
+UI_ParseCarriageList
+===============
+*/
+static void UI_ParseCarriageList( int *weapons, int *upgrades )
+{
+ int i;
+ char carriageCvar[ MAX_TOKEN_CHARS ];
+ char *iterator;
+ char buffer[ MAX_TOKEN_CHARS ];
+ char *bufPointer;
+
+ trap_Cvar_VariableStringBuffer( "ui_carriage", carriageCvar, sizeof( carriageCvar ) );
+ iterator = carriageCvar;
+
+ if( weapons )
+ *weapons = 0;
+
+ if( upgrades )
+ *upgrades = 0;
+
+ //simple parser to give rise to weapon/upgrade list
+ while( iterator && iterator[ 0 ] != '$' )
+ {
+ bufPointer = buffer;
+
+ if( iterator[ 0 ] == 'W' )
+ {
+ iterator++;
+
+ while( iterator[ 0 ] != ' ' )
+ *bufPointer++ = *iterator++;
+
+ *bufPointer++ = '\n';
+
+ i = atoi( buffer );
+
+ if( weapons )
+ *weapons |= ( 1 << i );
+ }
+ else if( iterator[ 0 ] == 'U' )
+ {
+ iterator++;
+
+ while( iterator[ 0 ] != ' ' )
+ *bufPointer++ = *iterator++;
+
+ *bufPointer++ = '\n';
+
+ i = atoi( buffer );
+
+ if( upgrades )
+ *upgrades |= ( 1 << i );
+ }
+
+ iterator++;
+ }
+}
+
+/*
+===============
+UI_LoadTremHumanArmouryBuys
+===============
+*/
+static void UI_LoadTremHumanArmouryBuys( void )
+{
+ int i, j = 0;
+ stage_t stage = UI_GetCurrentHumanStage( );
+ int weapons, upgrades;
+ int slots = 0;
+
+ UI_ParseCarriageList( &weapons, &upgrades );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( weapons & ( 1 << i ) )
+ slots |= BG_FindSlotsForWeapon( i );
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( upgrades & ( 1 << i ) )
+ slots |= BG_FindSlotsForUpgrade( i );
+ }
+
+ uiInfo.tremHumanArmouryBuyCount = 0;
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_FindTeamForWeapon( i ) == WUT_HUMANS &&
+ BG_FindPurchasableForWeapon( i ) &&
+ BG_FindStagesForWeapon( i, stage ) &&
+ BG_WeaponIsAllowed( i ) &&
+ !( BG_FindSlotsForWeapon( i ) & slots ) &&
+ !( weapons & ( 1 << i ) ) )
+ {
+ uiInfo.tremHumanArmouryBuyList[ j ].text =
+ String_Alloc( BG_FindHumanNameForWeapon( i ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].cmd =
+ String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForWeapon( i ) ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmouryBuyCount++;
+ }
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_FindTeamForUpgrade( i ) == WUT_HUMANS &&
+ BG_FindPurchasableForUpgrade( i ) &&
+ BG_FindStagesForUpgrade( i, stage ) &&
+ BG_UpgradeIsAllowed( i ) &&
+ !( BG_FindSlotsForUpgrade( i ) & slots ) &&
+ !( upgrades & ( 1 << i ) ) )
+ {
+ uiInfo.tremHumanArmouryBuyList[ j ].text =
+ String_Alloc( BG_FindHumanNameForUpgrade( i ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].cmd =
+ String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForUpgrade( i ) ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmouryBuyCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremHumanArmourySells
+===============
+*/
+static void UI_LoadTremHumanArmourySells( void )
+{
+ int weapons, upgrades;
+ int i, j = 0;
+
+ uiInfo.tremHumanArmourySellCount = 0;
+ UI_ParseCarriageList( &weapons, &upgrades );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( weapons & ( 1 << i ) )
+ {
+ uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForWeapon( i ) );
+ uiInfo.tremHumanArmourySellList[ j ].cmd =
+ String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForWeapon( i ) ) );
+ uiInfo.tremHumanArmourySellList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmourySellCount++;
+ }
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( upgrades & ( 1 << i ) )
+ {
+ uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForUpgrade( i ) );
+ uiInfo.tremHumanArmourySellList[ j ].cmd =
+ String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForUpgrade( i ) ) );
+ uiInfo.tremHumanArmourySellList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmourySellCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremAlienUpgrades
+===============
+*/
+static void UI_LoadTremAlienUpgrades( void )
+{
+ int i, j = 0;
+ int class, credits;
+ char ui_currentClass[ MAX_STRING_CHARS ];
+ stage_t stage = UI_GetCurrentAlienStage( );
+
+ trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS );
+ sscanf( ui_currentClass, "%d %d", &class, &credits );
+
+ uiInfo.tremAlienUpgradeCount = 0;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ if( BG_ClassCanEvolveFromTo( class, i, credits, 0 ) >= 0 &&
+ BG_FindStagesForClass( i, stage ) &&
+ BG_ClassIsAllowed( i ) )
+ {
+ uiInfo.tremAlienUpgradeList[ j ].text = String_Alloc( BG_FindHumanNameForClassNum( i ) );
+ uiInfo.tremAlienUpgradeList[ j ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( i ) ) );
+ uiInfo.tremAlienUpgradeList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( i ) ) );
+
+ j++;
+
+ uiInfo.tremAlienUpgradeCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremAlienBuilds
+===============
+*/
+static void UI_LoadTremAlienBuilds( void )
+{
+ int weapons;
+ int i, j = 0;
+ stage_t stage;
+
+ UI_ParseCarriageList( &weapons, NULL );
+ stage = UI_GetCurrentAlienStage( );
+
+ uiInfo.tremAlienBuildCount = 0;
+
+ for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ if( BG_FindTeamForBuildable( i ) == BIT_ALIENS &&
+ BG_FindBuildWeaponForBuildable( i ) & weapons &&
+ BG_FindStagesForBuildable( i, stage ) &&
+ BG_BuildableIsAllowed( i ) )
+ {
+ uiInfo.tremAlienBuildList[ j ].text =
+ String_Alloc( BG_FindHumanNameForBuildable( i ) );
+ uiInfo.tremAlienBuildList[ j ].cmd =
+ String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) );
+ uiInfo.tremAlienBuildList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) );
+
+ j++;
+
+ uiInfo.tremAlienBuildCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremHumanBuilds
+===============
+*/
+static void UI_LoadTremHumanBuilds( void )
+{
+ int weapons;
+ int i, j = 0;
+ stage_t stage;
+
+ UI_ParseCarriageList( &weapons, NULL );
+ stage = UI_GetCurrentHumanStage( );
+
+ uiInfo.tremHumanBuildCount = 0;
+
+ for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ if( BG_FindTeamForBuildable( i ) == BIT_HUMANS &&
+ BG_FindBuildWeaponForBuildable( i ) & weapons &&
+ BG_FindStagesForBuildable( i, stage ) &&
+ BG_BuildableIsAllowed( i ) )
+ {
+ uiInfo.tremHumanBuildList[ j ].text =
+ String_Alloc( BG_FindHumanNameForBuildable( i ) );
+ uiInfo.tremHumanBuildList[ j ].cmd =
+ String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) );
+ uiInfo.tremHumanBuildList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanBuildCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadMods
+===============
+*/
+static void UI_LoadMods( void ) {
+ int numdirs;
+ char dirlist[2048];
+ char *dirptr;
+ char *descptr;
+ int i;
+ int dirlen;
+
+ uiInfo.modCount = 0;
+ numdirs = trap_FS_GetFileList( "$modlist", "", dirlist, sizeof(dirlist) );
+ dirptr = dirlist;
+ for( i = 0; i < numdirs; i++ ) {
+ dirlen = strlen( dirptr ) + 1;
+ descptr = dirptr + dirlen;
+ uiInfo.modList[uiInfo.modCount].modName = String_Alloc(dirptr);
+ uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc(descptr);
+ dirptr += dirlen + strlen(descptr) + 1;
+ uiInfo.modCount++;
+ if (uiInfo.modCount >= MAX_MODS) {
+ break;
+ }
+ }
+
+}
+
+
+/*
+===============
+UI_LoadMovies
+===============
+*/
+static void UI_LoadMovies( void ) {
+ char movielist[4096];
+ char *moviename;
+ int i, len;
+
+ uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 );
+
+ if (uiInfo.movieCount) {
+ if (uiInfo.movieCount > MAX_MOVIES) {
+ uiInfo.movieCount = MAX_MOVIES;
+ }
+ moviename = movielist;
+ for ( i = 0; i < uiInfo.movieCount; i++ ) {
+ len = strlen( moviename );
+ if (!Q_stricmp(moviename + len - 4,".roq")) {
+ moviename[len-4] = '\0';
+ }
+ Q_strupr(moviename);
+ uiInfo.movieList[i] = String_Alloc(moviename);
+ moviename += len + 1;
+ }
+ }
+
+}
+
+
+
+/*
+===============
+UI_LoadDemos
+===============
+*/
+static void UI_LoadDemos( void ) {
+ char demolist[4096];
+ char demoExt[32];
+ char *demoname;
+ int i, len;
+
+ Com_sprintf(demoExt, sizeof(demoExt), "dm_%d", (int)trap_Cvar_VariableValue("protocol"));
+
+ uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 );
+
+ Com_sprintf(demoExt, sizeof(demoExt), ".dm_%d", (int)trap_Cvar_VariableValue("protocol"));
+
+ if (uiInfo.demoCount) {
+ if (uiInfo.demoCount > MAX_DEMOS) {
+ uiInfo.demoCount = MAX_DEMOS;
+ }
+ demoname = demolist;
+ for ( i = 0; i < uiInfo.demoCount; i++ ) {
+ len = strlen( demoname );
+ if (!Q_stricmp(demoname + len - strlen(demoExt), demoExt)) {
+ demoname[len-strlen(demoExt)] = '\0';
+ }
+ Q_strupr(demoname);
+ uiInfo.demoList[i] = String_Alloc(demoname);
+ demoname += len + 1;
+ }
+ }
+
+}
+
+
+static qboolean UI_SetNextMap(int actual, int index) {
+ int i;
+ for (i = actual + 1; i < uiInfo.mapCount; i++) {
+ if (uiInfo.mapList[i].active) {
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, index + 1, "skirmish");
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+
+static void UI_StartSkirmish(qboolean next) {
+ int i, k, g, delay, temp;
+ float skill;
+ char buff[MAX_STRING_CHARS];
+
+ if (next) {
+ int actual;
+ int index = trap_Cvar_VariableValue("ui_mapIndex");
+ UI_MapCountByGameType(qtrue);
+ UI_SelectedMap(index, &actual);
+ if (UI_SetNextMap(actual, index)) {
+ } else {
+ UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse);
+ UI_MapCountByGameType(qtrue);
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, "skirmish");
+ }
+ }
+
+ g = uiInfo.gameTypes[ui_gameType.integer].gtEnum;
+ trap_Cvar_SetValue( "g_gametype", g );
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName) );
+ skill = trap_Cvar_VariableValue( "g_spSkill" );
+ trap_Cvar_Set("ui_scoreMap", uiInfo.mapList[ui_currentMap.integer].mapName);
+
+ k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+
+ trap_Cvar_Set("ui_singlePlayerActive", "1");
+
+ // set up sp overrides, will be replaced on postgame
+ temp = trap_Cvar_VariableValue( "capturelimit" );
+ trap_Cvar_Set("ui_saveCaptureLimit", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "fraglimit" );
+ trap_Cvar_Set("ui_saveFragLimit", va("%i", temp));
+
+ UI_SetCapFragLimits(qfalse);
+
+ temp = trap_Cvar_VariableValue( "cg_drawTimer" );
+ trap_Cvar_Set("ui_drawTimer", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "g_doWarmup" );
+ trap_Cvar_Set("ui_doWarmup", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "g_friendlyFire" );
+ trap_Cvar_Set("ui_friendlyFire", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "sv_maxClients" );
+ trap_Cvar_Set("ui_maxClients", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "g_warmup" );
+ trap_Cvar_Set("ui_Warmup", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "sv_pure" );
+ trap_Cvar_Set("ui_pure", va("%i", temp));
+
+ trap_Cvar_Set("cg_cameraOrbit", "0");
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set("cg_drawTimer", "1");
+ trap_Cvar_Set("g_doWarmup", "1");
+ trap_Cvar_Set("g_warmup", "15");
+ trap_Cvar_Set("sv_pure", "0");
+ trap_Cvar_Set("g_friendlyFire", "0");
+ trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName"));
+ trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName"));
+
+ if (trap_Cvar_VariableValue("ui_recordSPDemo")) {
+ Com_sprintf(buff, MAX_STRING_CHARS, "%s_%i", uiInfo.mapList[ui_currentMap.integer].mapLoadName, g);
+ trap_Cvar_Set("ui_recordSPDemoName", buff);
+ }
+
+ delay = 500;
+
+ {
+ temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2;
+ trap_Cvar_Set("sv_maxClients", va("%d", temp));
+ for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers; i++) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ delay += 500;
+ }
+ k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers-1; i++) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ delay += 500;
+ }
+ }
+}
+
+static void UI_Update(const char *name) {
+ int val = trap_Cvar_VariableValue(name);
+
+ if (Q_stricmp(name, "ui_SetName") == 0) {
+ trap_Cvar_Set( "name", UI_Cvar_VariableString("ui_Name"));
+ } else if (Q_stricmp(name, "ui_setRate") == 0) {
+ float rate = trap_Cvar_VariableValue("rate");
+ if (rate >= 5000) {
+ trap_Cvar_Set("cl_maxpackets", "30");
+ trap_Cvar_Set("cl_packetdup", "1");
+ } else if (rate >= 4000) {
+ trap_Cvar_Set("cl_maxpackets", "15");
+ trap_Cvar_Set("cl_packetdup", "2"); // favor less prediction errors when there's packet loss
+ } else {
+ trap_Cvar_Set("cl_maxpackets", "15");
+ trap_Cvar_Set("cl_packetdup", "1"); // favor lower bandwidth
+ }
+ } else if (Q_stricmp(name, "ui_GetName") == 0) {
+ trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name"));
+ } else if (Q_stricmp(name, "r_colorbits") == 0) {
+ switch (val) {
+ case 0:
+ trap_Cvar_SetValue( "r_depthbits", 0 );
+ trap_Cvar_SetValue( "r_stencilbits", 0 );
+ break;
+ case 16:
+ trap_Cvar_SetValue( "r_depthbits", 16 );
+ trap_Cvar_SetValue( "r_stencilbits", 0 );
+ break;
+ case 32:
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ break;
+ }
+ } else if (Q_stricmp(name, "r_lodbias") == 0) {
+ switch (val) {
+ case 0:
+ trap_Cvar_SetValue( "r_subdivisions", 4 );
+ break;
+ case 1:
+ trap_Cvar_SetValue( "r_subdivisions", 12 );
+ break;
+ case 2:
+ trap_Cvar_SetValue( "r_subdivisions", 20 );
+ break;
+ }
+ } else if (Q_stricmp(name, "ui_glCustom") == 0) {
+ switch (val) {
+ case 0: // high quality
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 4 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 0 );
+ trap_Cvar_SetValue( "r_colorbits", 32 );
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ trap_Cvar_SetValue( "r_picmip", 0 );
+ trap_Cvar_SetValue( "r_mode", 4 );
+ trap_Cvar_SetValue( "r_texturebits", 32 );
+ trap_Cvar_SetValue( "r_fastSky", 0 );
+ trap_Cvar_SetValue( "r_inGameVideo", 1 );
+ trap_Cvar_SetValue( "cg_shadows", 1 );
+ trap_Cvar_SetValue( "cg_brassTime", 2500 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 1 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
+ break;
+ case 1: // normal
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 12 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 0 );
+ trap_Cvar_SetValue( "r_colorbits", 0 );
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ trap_Cvar_SetValue( "r_picmip", 1 );
+ trap_Cvar_SetValue( "r_mode", 3 );
+ trap_Cvar_SetValue( "r_texturebits", 0 );
+ trap_Cvar_SetValue( "r_fastSky", 0 );
+ trap_Cvar_SetValue( "r_inGameVideo", 1 );
+ trap_Cvar_SetValue( "cg_brassTime", 2500 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
+ trap_Cvar_SetValue( "cg_shadows", 0 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 0 );
+ break;
+ case 2: // fast
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 8 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 1 );
+ trap_Cvar_SetValue( "r_colorbits", 0 );
+ trap_Cvar_SetValue( "r_depthbits", 0 );
+ trap_Cvar_SetValue( "r_picmip", 1 );
+ trap_Cvar_SetValue( "r_mode", 3 );
+ trap_Cvar_SetValue( "r_texturebits", 0 );
+ trap_Cvar_SetValue( "cg_shadows", 0 );
+ trap_Cvar_SetValue( "r_fastSky", 1 );
+ trap_Cvar_SetValue( "r_inGameVideo", 0 );
+ trap_Cvar_SetValue( "cg_brassTime", 0 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 0 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
+ break;
+ case 3: // fastest
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 20 );
+ trap_Cvar_SetValue( "r_vertexlight", 1 );
+ trap_Cvar_SetValue( "r_lodbias", 2 );
+ trap_Cvar_SetValue( "r_colorbits", 16 );
+ trap_Cvar_SetValue( "r_depthbits", 16 );
+ trap_Cvar_SetValue( "r_mode", 3 );
+ trap_Cvar_SetValue( "r_picmip", 2 );
+ trap_Cvar_SetValue( "r_texturebits", 16 );
+ trap_Cvar_SetValue( "cg_shadows", 0 );
+ trap_Cvar_SetValue( "cg_brassTime", 0 );
+ trap_Cvar_SetValue( "r_fastSky", 1 );
+ trap_Cvar_SetValue( "r_inGameVideo", 0 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 0 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
+ break;
+ }
+ } else if (Q_stricmp(name, "ui_mousePitch") == 0) {
+ if (val == 0) {
+ trap_Cvar_SetValue( "m_pitch", 0.022f );
+ } else {
+ trap_Cvar_SetValue( "m_pitch", -0.022f );
+ }
+ }
+}
+
+static void UI_RunMenuScript(char **args) {
+ const char *name, *name2;
+ char buff[1024];
+ const char *cmd;
+
+ if (String_Parse(args, &name)) {
+ if (Q_stricmp(name, "StartServer") == 0) {
+ int i, clients, oldclients;
+ float skill;
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set("cg_cameraOrbit", "0");
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) );
+ trap_Cvar_SetValue( "g_gametype", Com_Clamp( 0, 8, uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) );
+ trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName"));
+ trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName"));
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) );
+ skill = trap_Cvar_VariableValue( "g_spSkill" );
+ // set max clients based on spots
+ oldclients = trap_Cvar_VariableValue( "sv_maxClients" );
+ clients = 0;
+ for (i = 0; i < PLAYERS_PER_TEAM; i++) {
+ int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1));
+ if (bot >= 0) {
+ clients++;
+ }
+ bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1));
+ if (bot >= 0) {
+ clients++;
+ }
+ }
+ if (clients == 0) {
+ clients = 8;
+ }
+
+ if (oldclients > clients) {
+ clients = oldclients;
+ }
+
+ trap_Cvar_Set("sv_maxClients", va("%d",clients));
+
+ for (i = 0; i < PLAYERS_PER_TEAM; i++) {
+ int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1));
+ if (bot > 1) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ }
+ bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1));
+ if (bot > 1) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ }
+ }
+ } else if (Q_stricmp(name, "updateSPMenu") == 0) {
+ UI_SetCapFragLimits(qtrue);
+ UI_MapCountByGameType(qtrue);
+ ui_mapIndex.integer = UI_GetIndexFromSelection(ui_currentMap.integer);
+ trap_Cvar_Set("ui_mapIndex", va("%d", ui_mapIndex.integer));
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, ui_mapIndex.integer, "skirmish");
+ UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse);
+ UI_GameType_HandleKey(0, NULL, K_MOUSE2, qfalse);
+ } else if (Q_stricmp(name, "resetDefaults") == 0) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, "exec default.cfg\n");
+ trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n");
+ Controls_SetDefaults();
+ trap_Cvar_Set("com_introPlayed", "1" );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" );
+ } else if (Q_stricmp(name, "loadArenas") == 0) {
+ UI_LoadArenas();
+ UI_MapCountByGameType(qfalse);
+ Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, "createserver");
+ } else if (Q_stricmp(name, "loadServerInfo") == 0) {
+ UI_ServerInfo();
+ } else if (Q_stricmp(name, "saveControls") == 0) {
+ Controls_SetConfig(qtrue);
+ } else if (Q_stricmp(name, "loadControls") == 0) {
+ Controls_GetConfig();
+ } else if (Q_stricmp(name, "clearError") == 0) {
+ trap_Cvar_Set("com_errorMessage", "");
+ } else if (Q_stricmp(name, "loadGameInfo") == 0) {
+/* UI_ParseGameInfo("gameinfo.txt");
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);*/
+ } else if (Q_stricmp(name, "resetScores") == 0) {
+ UI_ClearScores();
+ } else if (Q_stricmp(name, "RefreshServers") == 0) {
+ UI_StartServerRefresh(qtrue);
+ UI_BuildServerDisplayList(qtrue);
+ } else if (Q_stricmp(name, "InitServerList") == 0) {
+ int time = trap_RealTime( NULL );
+ int last;
+ int sortColumn;
+
+ // set up default sorting
+ if(!uiInfo.serverStatus.sorted && Int_Parse(args, &sortColumn))
+ {
+ uiInfo.serverStatus.sortKey = sortColumn;
+ uiInfo.serverStatus.sortDir = 0;
+ }
+
+ // refresh if older than 3 days or if list is empty
+ last = atoi( UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i_time",
+ ui_netSource.integer ) ) );
+ if( trap_LAN_GetServerCount( ui_netSource.integer ) < 1 ||
+ ( time - last ) > 3600 )
+ {
+ UI_StartServerRefresh(qtrue);
+ UI_BuildServerDisplayList(qtrue);
+ }
+ } else if (Q_stricmp(name, "RefreshFilter") == 0) {
+ UI_StartServerRefresh(qfalse);
+ UI_BuildServerDisplayList(qtrue);
+ } else if (Q_stricmp(name, "RunSPDemo") == 0) {
+ if (uiInfo.demoAvailable) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s_%i\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum));
+ }
+ } else if (Q_stricmp(name, "LoadDemos") == 0) {
+ UI_LoadDemos();
+ } else if (Q_stricmp(name, "LoadMovies") == 0) {
+ UI_LoadMovies();
+ } else if (Q_stricmp(name, "LoadMods") == 0) {
+ UI_LoadMods();
+ }
+
+//TA: tremulous menus
+ else if( Q_stricmp( name, "LoadTeams" ) == 0 )
+ UI_LoadTremTeams( );
+ else if( Q_stricmp( name, "JoinTeam" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanItems" ) == 0 )
+ UI_LoadTremHumanItems( );
+ else if( Q_stricmp( name, "SpawnWithHumanItem" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienClasses" ) == 0 )
+ UI_LoadTremAlienClasses( );
+ else if( Q_stricmp( name, "SpawnAsAlienClass" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanArmouryBuys" ) == 0 )
+ UI_LoadTremHumanArmouryBuys( );
+ else if( Q_stricmp( name, "BuyFromArmoury" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanArmourySells" ) == 0 )
+ UI_LoadTremHumanArmourySells( );
+ else if( Q_stricmp( name, "SellToArmoury" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienUpgrades" ) == 0 )
+ {
+ UI_LoadTremAlienUpgrades( );
+
+ //disallow the menu if it would be empty
+ if( uiInfo.tremAlienUpgradeCount <= 0 )
+ Menus_CloseAll( );
+ }
+ else if( Q_stricmp( name, "UpgradeToNewClass" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienBuilds" ) == 0 )
+ UI_LoadTremAlienBuilds( );
+ else if( Q_stricmp( name, "BuildAlienBuildable" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanBuilds" ) == 0 )
+ UI_LoadTremHumanBuilds( );
+ else if( Q_stricmp( name, "BuildHumanBuildable" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "Say" ) == 0 )
+ {
+ char buffer[ MAX_CVAR_VALUE_STRING ];
+ trap_Cvar_VariableStringBuffer( "ui_sayBuffer", buffer, sizeof( buffer ) );
+
+ if( !buffer[ 0 ] )
+ ;
+ else if( ui_chatCommands.integer && ( buffer[ 0 ] == '/' ||
+ buffer[ 0 ] == '\\' ) )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "%s\n", buffer + 1 ) );
+ }
+ else if( uiInfo.chatTeam )
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "say_team \"%s\"\n", buffer ) );
+ else
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) );
+ }
+ else if( Q_stricmp( name, "PTRCRestore" ) == 0 )
+ {
+ int len;
+ char text[ 16 ];
+ fileHandle_t f;
+ char command[ 32 ];
+
+ // load the file
+ len = trap_FS_FOpenFile( "ptrc.cfg", &f, FS_READ );
+
+ if( len > 0 && ( len < sizeof( text ) - 1 ) )
+ {
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ Com_sprintf( command, 32, "ptrcrestore %s", text );
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, command );
+ }
+ }
+//TA: tremulous menus
+
+ else if (Q_stricmp(name, "playMovie") == 0) {
+ if (uiInfo.previewMovie >= 0) {
+ trap_CIN_StopCinematic(uiInfo.previewMovie);
+ }
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex]));
+ } else if (Q_stricmp(name, "RunMod") == 0) {
+ trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName);
+ trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" );
+ } else if (Q_stricmp(name, "RunDemo") == 0) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s\n", uiInfo.demoList[uiInfo.demoIndex]));
+ } else if (Q_stricmp(name, "Tremulous") == 0) {
+ trap_Cvar_Set( "fs_game", "");
+ trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" );
+ } else if (Q_stricmp(name, "closeJoin") == 0) {
+ if (uiInfo.serverStatus.refreshActive) {
+ UI_StopServerRefresh();
+ uiInfo.serverStatus.nextDisplayRefresh = 0;
+ uiInfo.nextServerStatusRefresh = 0;
+ uiInfo.nextFindPlayerRefresh = 0;
+ UI_BuildServerDisplayList(qtrue);
+ } else {
+ Menus_CloseByName("joinserver");
+ Menus_OpenByName("main");
+ }
+ } else if (Q_stricmp(name, "StopRefresh") == 0) {
+ UI_StopServerRefresh();
+ uiInfo.serverStatus.nextDisplayRefresh = 0;
+ uiInfo.nextServerStatusRefresh = 0;
+ uiInfo.nextFindPlayerRefresh = 0;
+ } else if (Q_stricmp(name, "UpdateFilter") == 0) {
+ if (ui_netSource.integer == AS_LOCAL) {
+ UI_StartServerRefresh(qtrue);
+ }
+ UI_BuildServerDisplayList(qtrue);
+ UI_FeederSelection(FEEDER_SERVERS, 0);
+ } else if (Q_stricmp(name, "ServerStatus") == 0) {
+ trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, sizeof(uiInfo.serverStatusAddress));
+ UI_BuildServerStatus(qtrue);
+ } else if (Q_stricmp(name, "FoundPlayerServerStatus") == 0) {
+ Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress));
+ UI_BuildServerStatus(qtrue);
+ Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL);
+ } else if (Q_stricmp(name, "FindPlayer") == 0) {
+ UI_BuildFindPlayerList(qtrue);
+ // clear the displayed server status info
+ uiInfo.serverStatusInfo.numLines = 0;
+ Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL);
+ } else if (Q_stricmp(name, "JoinServer") == 0) {
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set("cg_cameraOrbit", "0");
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ if (uiInfo.serverStatus.currentServer >= 0 && uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers) {
+ trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) );
+ }
+ } else if (Q_stricmp(name, "FoundPlayerJoinServer") == 0) {
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ if (uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) );
+ }
+ } else if (Q_stricmp(name, "Quit") == 0) {
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ trap_Cmd_ExecuteText( EXEC_NOW, "quit");
+ } else if (Q_stricmp(name, "Controls") == 0) {
+ trap_Cvar_Set( "cl_paused", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_CloseAll();
+ Menus_ActivateByName("setup_menu2");
+ } else if (Q_stricmp(name, "Leave") == 0) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_CloseAll();
+ Menus_ActivateByName("main");
+ } else if (Q_stricmp(name, "ServerSort") == 0) {
+ int sortColumn;
+ if (Int_Parse(args, &sortColumn)) {
+ // if same column we're already sorting on then flip the direction
+ if (sortColumn == uiInfo.serverStatus.sortKey) {
+ uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir;
+ }
+ // make sure we sort again
+ UI_ServersSort(sortColumn, qtrue);
+ uiInfo.serverStatus.sorted = qtrue;
+ }
+ } else if (Q_stricmp(name, "nextSkirmish") == 0) {
+ UI_StartSkirmish(qtrue);
+ } else if (Q_stricmp(name, "SkirmishStart") == 0) {
+ UI_StartSkirmish(qfalse);
+ } else if (Q_stricmp(name, "closeingame") == 0) {
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ } else if (Q_stricmp(name, "voteMap") == 0) {
+ if (ui_currentNetMap.integer >=0 && ui_currentNetMap.integer < uiInfo.mapCount) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote map %s\n",uiInfo.mapList[ui_currentNetMap.integer].mapLoadName) );
+ }
+ }
+ else if( Q_stricmp( name, "voteKick" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote kick %d\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteMute" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote mute %d\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteUnMute" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote unmute %d\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamKick" ) == 0 )
+ {
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote kick %d\n",
+ uiInfo.teamClientNums[ uiInfo.teamIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamDenyBuild" ) == 0 )
+ {
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote denybuild %d\n",
+ uiInfo.teamClientNums[ uiInfo.teamIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamAllowBuild" ) == 0 )
+ {
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote allowbuild %d\n",
+ uiInfo.teamClientNums[ uiInfo.teamIndex ] ) );
+ }
+ }
+ else if (Q_stricmp(name, "addFavorite") == 0) {
+ if (ui_netSource.integer != AS_FAVORITES) {
+ char name[MAX_NAME_LENGTH];
+ char addr[MAX_NAME_LENGTH];
+ int res;
+
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS);
+ name[0] = addr[0] = '\0';
+ Q_strncpyz(name, Info_ValueForKey(buff, "hostname"), MAX_NAME_LENGTH);
+ Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH);
+ if (strlen(name) > 0 && strlen(addr) > 0) {
+ res = trap_LAN_AddServer(AS_FAVORITES, name, addr);
+ if (res == 0) {
+ // server already in the list
+ Com_Printf("Favorite already in list\n");
+ }
+ else if (res == -1) {
+ // list full
+ Com_Printf("Favorite list full\n");
+ }
+ else {
+ // successfully added
+ Com_Printf("Added favorite server %s\n", addr);
+ }
+ }
+ }
+ } else if (Q_stricmp(name, "deleteFavorite") == 0) {
+ if (ui_netSource.integer == AS_FAVORITES) {
+ char addr[MAX_NAME_LENGTH];
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS);
+ addr[0] = '\0';
+ Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH);
+ if (strlen(addr) > 0) {
+ trap_LAN_RemoveServer(AS_FAVORITES, addr);
+ }
+ }
+ } else if (Q_stricmp(name, "createFavorite") == 0) {
+ if (ui_netSource.integer == AS_FAVORITES) {
+ char name[MAX_NAME_LENGTH];
+ char addr[MAX_NAME_LENGTH];
+ int res;
+
+ name[0] = addr[0] = '\0';
+ Q_strncpyz(name, UI_Cvar_VariableString("ui_favoriteName"), MAX_NAME_LENGTH);
+ Q_strncpyz(addr, UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH);
+ if (strlen(name) > 0 && strlen(addr) > 0) {
+ res = trap_LAN_AddServer(AS_FAVORITES, name, addr);
+ if (res == 0) {
+ // server already in the list
+ Com_Printf("Favorite already in list\n");
+ }
+ else if (res == -1) {
+ // list full
+ Com_Printf("Favorite list full\n");
+ }
+ else {
+ // successfully added
+ Com_Printf("Added favorite server %s\n", addr);
+ }
+ }
+ }
+ } else if (Q_stricmp(name, "orders") == 0) {
+ const char *orders;
+ if (String_Parse(args, &orders)) {
+ int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (selectedPlayer < uiInfo.myTeamCount) {
+ strcpy(buff, orders);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ } else {
+ int i;
+ for (i = 0; i < uiInfo.myTeamCount; i++) {
+ if (Q_stricmp(UI_Cvar_VariableString("name"), uiInfo.teamNames[i]) == 0) {
+ continue;
+ }
+ strcpy(buff, orders);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamNames[i]) );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ }
+ }
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ }
+ } else if (Q_stricmp(name, "voiceOrdersTeam") == 0) {
+ const char *orders;
+ if (String_Parse(args, &orders)) {
+ int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (selectedPlayer == uiInfo.myTeamCount) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, orders );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ }
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ }
+ } else if (Q_stricmp(name, "voiceOrders") == 0) {
+ const char *orders;
+ if (String_Parse(args, &orders)) {
+ int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (selectedPlayer < uiInfo.myTeamCount) {
+ strcpy(buff, orders);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ }
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ }
+ } else if (Q_stricmp(name, "glCustom") == 0) {
+ trap_Cvar_Set("ui_glCustom", "4");
+ } else if (Q_stricmp(name, "update") == 0) {
+ if (String_Parse(args, &name2))
+ UI_Update(name2);
+ } else if (Q_stricmp(name, "InitIgnoreList") == 0) {
+ UI_BuildPlayerList();
+ } else if (Q_stricmp(name, "ToggleIgnore") == 0) {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ else
+ {
+ BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ } else if (Q_stricmp(name, "IgnorePlayer") == 0) {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( !BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ } else if (Q_stricmp(name, "UnIgnorePlayer") == 0) {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ } else if (Q_stricmp(name, "setPbClStatus") == 0) {
+ int stat;
+ if ( Int_Parse( args, &stat ) )
+ trap_SetPbClStatus( stat );
+ }
+ else {
+ Com_Printf("unknown UI script %s\n", name);
+ }
+ }
+}
+
+static void UI_GetTeamColor(vec4_t *color) {
+}
+
+/*
+==================
+UI_MapCountByGameType
+==================
+*/
+static int UI_MapCountByGameType(qboolean singlePlayer) {
+ int i, c, game;
+ c = 0;
+ game = singlePlayer ? uiInfo.gameTypes[ui_gameType.integer].gtEnum : uiInfo.gameTypes[ui_netGameType.integer].gtEnum;
+
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ uiInfo.mapList[i].active = qfalse;
+ if ( uiInfo.mapList[i].typeBits & (1 << game)) {
+ if (singlePlayer) {
+ if (!(uiInfo.mapList[i].typeBits & (1 << 2))) {
+ continue;
+ }
+ }
+ c++;
+ uiInfo.mapList[i].active = qtrue;
+ }
+ }
+ return c;
+}
+
+qboolean UI_hasSkinForBase(const char *base, const char *team) {
+ char test[1024];
+
+ Com_sprintf( test, sizeof( test ), "models/players/%s/%s/lower_default.skin", base, team );
+
+ if (trap_FS_FOpenFile(test, NULL, FS_READ)) {
+ return qtrue;
+ }
+ Com_sprintf( test, sizeof( test ), "models/players/characters/%s/%s/lower_default.skin", base, team );
+
+ if (trap_FS_FOpenFile(test, NULL, FS_READ)) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+UI_MapCountByTeam
+==================
+*/
+static int UI_HeadCountByTeam( void ) {
+ static int init = 0;
+ int i, j, k, c, tIndex;
+
+ c = 0;
+ if (!init) {
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ uiInfo.characterList[i].reference = 0;
+ for (j = 0; j < uiInfo.teamCount; j++) {
+ if (UI_hasSkinForBase(uiInfo.characterList[i].base, uiInfo.teamList[j].teamName)) {
+ uiInfo.characterList[i].reference |= (1<<j);
+ }
+ }
+ }
+ init = 1;
+ }
+
+ tIndex = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+
+ // do names
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ uiInfo.characterList[i].active = qfalse;
+ for(j = 0; j < TEAM_MEMBERS; j++) {
+ if (uiInfo.teamList[tIndex].teamMembers[j] != NULL) {
+ if (uiInfo.characterList[i].reference&(1<<tIndex)) {// && Q_stricmp(uiInfo.teamList[tIndex].teamMembers[j], uiInfo.characterList[i].name)==0) {
+ uiInfo.characterList[i].active = qtrue;
+ c++;
+ break;
+ }
+ }
+ }
+ }
+
+ // and then aliases
+ for(j = 0; j < TEAM_MEMBERS; j++) {
+ for(k = 0; k < uiInfo.aliasCount; k++) {
+ if (uiInfo.aliasList[k].name != NULL) {
+ if (Q_stricmp(uiInfo.teamList[tIndex].teamMembers[j], uiInfo.aliasList[k].name)==0) {
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ if (uiInfo.characterList[i].headImage != -1 && uiInfo.characterList[i].reference&(1<<tIndex) && Q_stricmp(uiInfo.aliasList[k].ai, uiInfo.characterList[i].name)==0) {
+ if (uiInfo.characterList[i].active == qfalse) {
+ uiInfo.characterList[i].active = qtrue;
+ c++;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return c;
+}
+
+/*
+==================
+UI_InsertServerIntoDisplayList
+==================
+*/
+static void UI_InsertServerIntoDisplayList(int num, int position) {
+ int i;
+
+ if (position < 0 || position > uiInfo.serverStatus.numDisplayServers ) {
+ return;
+ }
+ //
+ uiInfo.serverStatus.numDisplayServers++;
+ for (i = uiInfo.serverStatus.numDisplayServers; i > position; i--) {
+ uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1];
+ }
+ uiInfo.serverStatus.displayServers[position] = num;
+}
+
+/*
+==================
+UI_RemoveServerFromDisplayList
+==================
+*/
+static void UI_RemoveServerFromDisplayList(int num) {
+ int i, j;
+
+ for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) {
+ if (uiInfo.serverStatus.displayServers[i] == num) {
+ uiInfo.serverStatus.numDisplayServers--;
+ for (j = i; j < uiInfo.serverStatus.numDisplayServers; j++) {
+ uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1];
+ }
+ return;
+ }
+ }
+}
+
+/*
+==================
+UI_BinaryServerInsertion
+==================
+*/
+static void UI_BinaryServerInsertion(int num) {
+ int mid, offset, res, len;
+
+ // use binary search to insert server
+ len = uiInfo.serverStatus.numDisplayServers;
+ mid = len;
+ offset = 0;
+ res = 0;
+ while(mid > 0) {
+ mid = len >> 1;
+ //
+ res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey,
+ uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset+mid]);
+ // if equal
+ if (res == 0) {
+ UI_InsertServerIntoDisplayList(num, offset+mid);
+ return;
+ }
+ // if larger
+ else if (res == 1) {
+ offset += mid;
+ len -= mid;
+ }
+ // if smaller
+ else {
+ len -= mid;
+ }
+ }
+ if (res == 1) {
+ offset++;
+ }
+ UI_InsertServerIntoDisplayList(num, offset);
+}
+
+/*
+==================
+UI_BuildServerDisplayList
+==================
+*/
+static void UI_BuildServerDisplayList(int force) {
+ int i, count, clients, maxClients, ping, game, len, visible;
+ char info[MAX_STRING_CHARS];
+// qboolean startRefresh = qtrue; TTimo: unused
+ static int numinvisible;
+
+ if (!(force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh)) {
+ return;
+ }
+ // if we shouldn't reset
+ if ( force == 2 ) {
+ force = 0;
+ }
+
+ // do motd updates here too
+ trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof(uiInfo.serverStatus.motd) );
+ len = strlen(uiInfo.serverStatus.motd);
+ if (len != uiInfo.serverStatus.motdLen) {
+ uiInfo.serverStatus.motdLen = len;
+ uiInfo.serverStatus.motdWidth = -1;
+ }
+
+ if (force) {
+ numinvisible = 0;
+ // clear number of displayed servers
+ uiInfo.serverStatus.numDisplayServers = 0;
+ uiInfo.serverStatus.numPlayersOnServers = 0;
+ // set list box index to zero
+ Menu_SetFeederSelection(NULL, FEEDER_SERVERS, 0, NULL);
+ // mark all servers as visible so we store ping updates for them
+ trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue);
+ }
+
+ // get the server count (comes from the master)
+ count = trap_LAN_GetServerCount(ui_netSource.integer);
+ if (count == -1 || (ui_netSource.integer == AS_LOCAL && count == 0) ) {
+ // still waiting on a response from the master
+ uiInfo.serverStatus.numDisplayServers = 0;
+ uiInfo.serverStatus.numPlayersOnServers = 0;
+ uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500;
+ return;
+ }
+
+ visible = qfalse;
+ for (i = 0; i < count; i++) {
+ // if we already got info for this server
+ if (!trap_LAN_ServerIsVisible(ui_netSource.integer, i)) {
+ continue;
+ }
+ visible = qtrue;
+ // get the ping for this server
+ ping = trap_LAN_GetServerPing(ui_netSource.integer, i);
+ if (ping > 0 || ui_netSource.integer == AS_FAVORITES) {
+
+ trap_LAN_GetServerInfo(ui_netSource.integer, i, info, MAX_STRING_CHARS);
+
+ clients = atoi(Info_ValueForKey(info, "clients"));
+ uiInfo.serverStatus.numPlayersOnServers += clients;
+
+ if (ui_browserShowEmpty.integer == 0) {
+ if (clients == 0) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ continue;
+ }
+ }
+
+ if (ui_browserShowFull.integer == 0) {
+ maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
+ if (clients == maxClients) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ continue;
+ }
+ }
+
+ if (uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum != -1) {
+ game = atoi(Info_ValueForKey(info, "gametype"));
+ if (game != uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ continue;
+ }
+ }
+
+ // make sure we never add a favorite server twice
+ if (ui_netSource.integer == AS_FAVORITES) {
+ UI_RemoveServerFromDisplayList(i);
+ }
+ // insert the server into the list
+ UI_BinaryServerInsertion(i);
+ // done with this server
+ if (ping > 0) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ numinvisible++;
+ }
+ }
+ }
+
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime;
+
+ // if there were no servers visible for ping updates
+ if (!visible) {
+// UI_StopServerRefresh();
+// uiInfo.serverStatus.nextDisplayRefresh = 0;
+ }
+}
+
+typedef struct
+{
+ char *name, *altName;
+} serverStatusCvar_t;
+
+serverStatusCvar_t serverStatusCvars[] = {
+ {"sv_hostname", "Name"},
+ {"Address", ""},
+ {"gamename", "Game name"},
+ {"g_gametype", "Game type"},
+ {"mapname", "Map"},
+ {"version", ""},
+ {"protocol", ""},
+ {"timelimit", ""},
+ {"fraglimit", ""},
+ {NULL, NULL}
+};
+
+/*
+==================
+UI_SortServerStatusInfo
+==================
+*/
+static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) {
+ int i, j, index;
+ char *tmp1, *tmp2;
+
+ // FIXME: if "gamename" == "baseq3" or "missionpack" then
+ // replace the gametype number by FFA, CTF etc.
+ //
+ index = 0;
+ for (i = 0; serverStatusCvars[i].name; i++) {
+ for (j = 0; j < info->numLines; j++) {
+ if ( !info->lines[j][1] || info->lines[j][1][0] ) {
+ continue;
+ }
+ if ( !Q_stricmp(serverStatusCvars[i].name, info->lines[j][0]) ) {
+ // swap lines
+ tmp1 = info->lines[index][0];
+ tmp2 = info->lines[index][3];
+ info->lines[index][0] = info->lines[j][0];
+ info->lines[index][3] = info->lines[j][3];
+ info->lines[j][0] = tmp1;
+ info->lines[j][3] = tmp2;
+ //
+ if ( strlen(serverStatusCvars[i].altName) ) {
+ info->lines[index][0] = serverStatusCvars[i].altName;
+ }
+ index++;
+ }
+ }
+ }
+}
+
+/*
+==================
+UI_GetServerStatusInfo
+==================
+*/
+static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) {
+ char *p, *score, *ping, *name;
+ int i, len;
+
+ if (!info) {
+ trap_LAN_ServerStatus( serverAddress, NULL, 0);
+ return qfalse;
+ }
+ memset(info, 0, sizeof(*info));
+ if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof(info->text)) ) {
+ Q_strncpyz(info->address, serverAddress, sizeof(info->address));
+ p = info->text;
+ info->numLines = 0;
+ info->lines[info->numLines][0] = "Address";
+ info->lines[info->numLines][1] = "";
+ info->lines[info->numLines][2] = "";
+ info->lines[info->numLines][3] = info->address;
+ info->numLines++;
+ // get the cvars
+ while (p && *p) {
+ p = strchr(p, '\\');
+ if (!p) break;
+ *p++ = '\0';
+ if (*p == '\\')
+ break;
+ info->lines[info->numLines][0] = p;
+ info->lines[info->numLines][1] = "";
+ info->lines[info->numLines][2] = "";
+ p = strchr(p, '\\');
+ if (!p) break;
+ *p++ = '\0';
+ info->lines[info->numLines][3] = p;
+
+ info->numLines++;
+ if (info->numLines >= MAX_SERVERSTATUS_LINES)
+ break;
+ }
+ // get the player list
+ if (info->numLines < MAX_SERVERSTATUS_LINES-3) {
+ // empty line
+ info->lines[info->numLines][0] = "";
+ info->lines[info->numLines][1] = "";
+ info->lines[info->numLines][2] = "";
+ info->lines[info->numLines][3] = "";
+ info->numLines++;
+ // header
+ info->lines[info->numLines][0] = "num";
+ info->lines[info->numLines][1] = "score";
+ info->lines[info->numLines][2] = "ping";
+ info->lines[info->numLines][3] = "name";
+ info->numLines++;
+ // parse players
+ i = 0;
+ len = 0;
+ while (p && *p) {
+ if (*p == '\\')
+ *p++ = '\0';
+ if (!p)
+ break;
+ score = p;
+ p = strchr(p, ' ');
+ if (!p)
+ break;
+ *p++ = '\0';
+ ping = p;
+ p = strchr(p, ' ');
+ if (!p)
+ break;
+ *p++ = '\0';
+ name = p;
+ Com_sprintf(&info->pings[len], sizeof(info->pings)-len, "%d", i);
+ info->lines[info->numLines][0] = &info->pings[len];
+ len += strlen(&info->pings[len]) + 1;
+ info->lines[info->numLines][1] = score;
+ info->lines[info->numLines][2] = ping;
+ info->lines[info->numLines][3] = name;
+ info->numLines++;
+ if (info->numLines >= MAX_SERVERSTATUS_LINES)
+ break;
+ p = strchr(p, '\\');
+ if (!p)
+ break;
+ *p++ = '\0';
+ //
+ i++;
+ }
+ }
+ UI_SortServerStatusInfo( info );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+stristr
+==================
+*/
+static char *stristr(char *str, char *charset) {
+ int i;
+
+ while(*str) {
+ for (i = 0; charset[i] && str[i]; i++) {
+ if (toupper(charset[i]) != toupper(str[i])) break;
+ }
+ if (!charset[i]) return str;
+ str++;
+ }
+ return NULL;
+}
+
+/*
+==================
+UI_BuildFindPlayerList
+==================
+*/
+static void UI_BuildFindPlayerList(qboolean force) {
+ static int numFound, numTimeOuts;
+ int i, j, resend;
+ serverStatusInfo_t info;
+ char name[MAX_NAME_LENGTH+2];
+ char infoString[MAX_STRING_CHARS];
+
+ if (!force) {
+ if (!uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime) {
+ return;
+ }
+ }
+ else {
+ memset(&uiInfo.pendingServerStatus, 0, sizeof(uiInfo.pendingServerStatus));
+ uiInfo.numFoundPlayerServers = 0;
+ uiInfo.currentFoundPlayerServer = 0;
+ trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof(uiInfo.findPlayerName));
+ Q_CleanStr(uiInfo.findPlayerName);
+ // should have a string of some length
+ if (!strlen(uiInfo.findPlayerName)) {
+ uiInfo.nextFindPlayerRefresh = 0;
+ return;
+ }
+ // set resend time
+ resend = ui_serverStatusTimeOut.integer / 2 - 10;
+ if (resend < 50) {
+ resend = 50;
+ }
+ trap_Cvar_Set("cl_serverStatusResendTime", va("%d", resend));
+ // reset all server status requests
+ trap_LAN_ServerStatus( NULL, NULL, 0);
+ //
+ uiInfo.numFoundPlayerServers = 1;
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
+ "searching %d...", uiInfo.pendingServerStatus.num);
+ numFound = 0;
+ numTimeOuts++;
+ }
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+ // if this pending server is valid
+ if (uiInfo.pendingServerStatus.server[i].valid) {
+ // try to get the server status for this server
+ if (UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) {
+ //
+ numFound++;
+ // parse through the server status lines
+ for (j = 0; j < info.numLines; j++) {
+ // should have ping info
+ if ( !info.lines[j][2] || !info.lines[j][2][0] ) {
+ continue;
+ }
+ // clean string first
+ Q_strncpyz(name, info.lines[j][3], sizeof(name));
+ Q_CleanStr(name);
+ // if the player name is a substring
+ if (stristr(name, uiInfo.findPlayerName)) {
+ // add to found server list if we have space (always leave space for a line with the number found)
+ if (uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS-1) {
+ //
+ Q_strncpyz(uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers-1],
+ uiInfo.pendingServerStatus.server[i].adrstr,
+ sizeof(uiInfo.foundPlayerServerAddresses[0]));
+ Q_strncpyz(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ uiInfo.pendingServerStatus.server[i].name,
+ sizeof(uiInfo.foundPlayerServerNames[0]));
+ uiInfo.numFoundPlayerServers++;
+ }
+ else {
+ // can't add any more so we're done
+ uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers;
+ }
+ }
+ }
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
+ "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound);
+ // retrieved the server status so reuse this spot
+ uiInfo.pendingServerStatus.server[i].valid = qfalse;
+ }
+ }
+ // if empty pending slot or timed out
+ if (!uiInfo.pendingServerStatus.server[i].valid ||
+ uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer) {
+ if (uiInfo.pendingServerStatus.server[i].valid) {
+ numTimeOuts++;
+ }
+ // reset server status request for this address
+ UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL );
+ // reuse pending slot
+ uiInfo.pendingServerStatus.server[i].valid = qfalse;
+ // if we didn't try to get the status of all servers in the main browser yet
+ if (uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers) {
+ uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime;
+ trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num],
+ uiInfo.pendingServerStatus.server[i].adrstr, sizeof(uiInfo.pendingServerStatus.server[i].adrstr));
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof(infoString));
+ Q_strncpyz(uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey(infoString, "hostname"), sizeof(uiInfo.pendingServerStatus.server[0].name));
+ uiInfo.pendingServerStatus.server[i].valid = qtrue;
+ uiInfo.pendingServerStatus.num++;
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
+ "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound);
+ }
+ }
+ }
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+ if (uiInfo.pendingServerStatus.server[i].valid) {
+ break;
+ }
+ }
+ // if still trying to retrieve server status info
+ if (i < MAX_SERVERSTATUSREQUESTS) {
+ uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25;
+ }
+ else {
+ // add a line that shows the number of servers found
+ if (!uiInfo.numFoundPlayerServers) {
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), "no servers found");
+ }
+ else {
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]),
+ "%d server%s found with player %s", uiInfo.numFoundPlayerServers-1,
+ uiInfo.numFoundPlayerServers == 2 ? "":"s", uiInfo.findPlayerName);
+ }
+ uiInfo.nextFindPlayerRefresh = 0;
+ // show the server status info for the selected server
+ UI_FeederSelection(FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer);
+ }
+}
+
+/*
+==================
+UI_BuildServerStatus
+==================
+*/
+static void UI_BuildServerStatus(qboolean force) {
+
+ if (uiInfo.nextFindPlayerRefresh) {
+ return;
+ }
+ if (!force) {
+ if (!uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime) {
+ return;
+ }
+ }
+ else {
+ Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL);
+ uiInfo.serverStatusInfo.numLines = 0;
+ // reset all server status requests
+ trap_LAN_ServerStatus( NULL, NULL, 0);
+ }
+ if (uiInfo.serverStatus.currentServer < 0 || uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || uiInfo.serverStatus.numDisplayServers == 0) {
+ return;
+ }
+ if (UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) {
+ uiInfo.nextServerStatusRefresh = 0;
+ UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL );
+ }
+ else {
+ uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500;
+ }
+}
+
+/*
+==================
+UI_FeederCount
+==================
+*/
+static int UI_FeederCount(float feederID) {
+
+ if (feederID == FEEDER_HEADS) {
+ return UI_HeadCountByTeam();
+ } else if (feederID == FEEDER_Q3HEADS) {
+ return uiInfo.q3HeadCount;
+ } else if (feederID == FEEDER_CINEMATICS) {
+ return uiInfo.movieCount;
+ } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) {
+ return UI_MapCountByGameType(feederID == FEEDER_MAPS ? qtrue : qfalse);
+ } else if (feederID == FEEDER_SERVERS) {
+ return uiInfo.serverStatus.numDisplayServers;
+ } else if (feederID == FEEDER_SERVERSTATUS) {
+ return uiInfo.serverStatusInfo.numLines;
+ } else if (feederID == FEEDER_FINDPLAYER) {
+ return uiInfo.numFoundPlayerServers;
+ } else if (feederID == FEEDER_PLAYER_LIST) {
+ if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+ return uiInfo.playerCount;
+ } else if (feederID == FEEDER_TEAM_LIST) {
+ if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+ return uiInfo.myTeamCount;
+ } else if (feederID == FEEDER_IGNORE_LIST) {
+ return uiInfo.playerCount;
+ } else if (feederID == FEEDER_MODS) {
+ return uiInfo.modCount;
+ } else if (feederID == FEEDER_DEMOS) {
+ return uiInfo.demoCount;
+ }
+
+//TA: tremulous menus
+ else if( feederID == FEEDER_TREMTEAMS )
+ return uiInfo.tremTeamCount;
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ return uiInfo.tremHumanItemCount;
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ return uiInfo.tremAlienClassCount;
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ return uiInfo.tremHumanArmouryBuyCount;
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ return uiInfo.tremHumanArmourySellCount;
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ return uiInfo.tremAlienUpgradeCount;
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ return uiInfo.tremAlienBuildCount;
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ return uiInfo.tremHumanBuildCount;
+//TA: tremulous menus
+
+ return 0;
+}
+
+static const char *UI_SelectedMap(int index, int *actual) {
+ int i, c;
+ c = 0;
+ *actual = 0;
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ if (uiInfo.mapList[i].active) {
+ if (c == index) {
+ *actual = i;
+ return uiInfo.mapList[i].mapName;
+ } else {
+ c++;
+ }
+ }
+ }
+ return "";
+}
+
+static const char *UI_SelectedHead(int index, int *actual) {
+ int i, c;
+ c = 0;
+ *actual = 0;
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ if (uiInfo.characterList[i].active) {
+ if (c == index) {
+ *actual = i;
+ return uiInfo.characterList[i].name;
+ } else {
+ c++;
+ }
+ }
+ }
+ return "";
+}
+
+static int UI_GetIndexFromSelection(int actual) {
+ int i, c;
+ c = 0;
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ if (uiInfo.mapList[i].active) {
+ if (i == actual) {
+ return c;
+ }
+ c++;
+ }
+ }
+ return 0;
+}
+
+static void UI_UpdatePendingPings( void ) {
+ trap_LAN_ResetPings(ui_netSource.integer);
+ uiInfo.serverStatus.refreshActive = qtrue;
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+
+}
+
+static const char *UI_FeederItemText(float feederID, int index, int column, qhandle_t *handle) {
+ static char info[MAX_STRING_CHARS];
+ static char hostname[1024];
+ static char clientBuff[32];
+ static int lastColumn = -1;
+ static int lastTime = 0;
+ *handle = -1;
+ if (feederID == FEEDER_HEADS) {
+ int actual;
+ return UI_SelectedHead(index, &actual);
+ } else if (feederID == FEEDER_Q3HEADS) {
+ if (index >= 0 && index < uiInfo.q3HeadCount) {
+ return uiInfo.q3HeadNames[index];
+ }
+ } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) {
+ int actual;
+ return UI_SelectedMap(index, &actual);
+ } else if (feederID == FEEDER_SERVERS) {
+ if (index >= 0 && index < uiInfo.serverStatus.numDisplayServers) {
+ int ping;
+ if (lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000) {
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS);
+ lastColumn = column;
+ lastTime = uiInfo.uiDC.realTime;
+ }
+
+ ping = atoi(Info_ValueForKey(info, "ping"));
+ if (ping == -1) {
+ // if we ever see a ping that is out of date, do a server refresh
+ // UI_UpdatePendingPings();
+ }
+ switch (column) {
+ case SORT_HOST :
+ if (ping <= 0) {
+ return Info_ValueForKey(info, "addr");
+ } else {
+ if ( ui_netSource.integer == AS_LOCAL ) {
+ Com_sprintf( hostname, sizeof(hostname), "%s [%s]",
+ Info_ValueForKey(info, "hostname"),
+ netnames[atoi(Info_ValueForKey(info, "nettype"))] );
+ return hostname;
+ }
+ else
+ {
+ char *text;
+
+ Com_sprintf( hostname, sizeof(hostname), "%s", Info_ValueForKey(info, "hostname"));
+
+ // Strip leading whitespace
+ text = hostname;
+ while( *text != '\0' && *text == ' ' )
+ text++;
+
+ return text;
+ }
+ }
+ case SORT_MAP :
+ return Info_ValueForKey(info, "mapname");
+ case SORT_CLIENTS :
+ Com_sprintf( clientBuff, sizeof(clientBuff), "%s (%s)", Info_ValueForKey(info, "clients"), Info_ValueForKey(info, "sv_maxclients"));
+ return clientBuff;
+ case SORT_PING :
+ if (ping <= 0) {
+ return "...";
+ } else {
+ return Info_ValueForKey(info, "ping");
+ }
+ }
+ }
+ } else if (feederID == FEEDER_SERVERSTATUS) {
+ if ( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) {
+ if ( column >= 0 && column < 4 ) {
+ return uiInfo.serverStatusInfo.lines[index][column];
+ }
+ }
+ } else if (feederID == FEEDER_FINDPLAYER) {
+ if ( index >= 0 && index < uiInfo.numFoundPlayerServers ) {
+ //return uiInfo.foundPlayerServerAddresses[index];
+ return uiInfo.foundPlayerServerNames[index];
+ }
+ } else if (feederID == FEEDER_PLAYER_LIST) {
+ if (index >= 0 && index < uiInfo.playerCount) {
+ return uiInfo.playerNames[index];
+ }
+ } else if (feederID == FEEDER_TEAM_LIST) {
+ if (index >= 0 && index < uiInfo.myTeamCount) {
+ return uiInfo.teamNames[index];
+ }
+ } else if (feederID == FEEDER_IGNORE_LIST) {
+ if (index >= 0 && index < uiInfo.playerCount) {
+ switch( column )
+ {
+ case 1:
+ // am I ignoring him
+ return ( BG_ClientListTest(&uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ index ] ) ) ? "X" : "";
+ case 2:
+ // is he ignoring me
+ return ( BG_ClientListTest( &uiInfo.ignoreList[ index ],
+ uiInfo.playerNumber ) ) ? "X" : "";
+ default:
+ return uiInfo.playerNames[index];
+ }
+ }
+ } else if (feederID == FEEDER_MODS) {
+ if (index >= 0 && index < uiInfo.modCount) {
+ if (uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr) {
+ return uiInfo.modList[index].modDescr;
+ } else {
+ return uiInfo.modList[index].modName;
+ }
+ }
+ } else if (feederID == FEEDER_CINEMATICS) {
+ if (index >= 0 && index < uiInfo.movieCount) {
+ return uiInfo.movieList[index];
+ }
+ } else if (feederID == FEEDER_DEMOS) {
+ if (index >= 0 && index < uiInfo.demoCount) {
+ return uiInfo.demoList[index];
+ }
+ }
+
+//TA: tremulous menus
+ else if( feederID == FEEDER_TREMTEAMS )
+ {
+ if( index >= 0 && index < uiInfo.tremTeamCount )
+ return uiInfo.tremTeamList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanItemCount )
+ return uiInfo.tremHumanItemList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ {
+ if( index >= 0 && index < uiInfo.tremAlienClassCount )
+ return uiInfo.tremAlienClassList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanArmouryBuyCount )
+ return uiInfo.tremHumanArmouryBuyList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanArmourySellCount )
+ return uiInfo.tremHumanArmourySellList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ {
+ if( index >= 0 && index < uiInfo.tremAlienUpgradeCount )
+ return uiInfo.tremAlienUpgradeList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ {
+ if( index >= 0 && index < uiInfo.tremAlienBuildCount )
+ return uiInfo.tremAlienBuildList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanBuildCount )
+ return uiInfo.tremHumanBuildList[ index ].text;
+ }
+//TA: tremulous menus
+
+ return "";
+}
+
+
+static qhandle_t UI_FeederItemImage(float feederID, int index) {
+ if (feederID == FEEDER_HEADS) {
+ int actual;
+ UI_SelectedHead(index, &actual);
+ index = actual;
+ if (index >= 0 && index < uiInfo.characterCount) {
+ if (uiInfo.characterList[index].headImage == -1) {
+ uiInfo.characterList[index].headImage = trap_R_RegisterShaderNoMip(uiInfo.characterList[index].imageName);
+ }
+ return uiInfo.characterList[index].headImage;
+ }
+ } else if (feederID == FEEDER_Q3HEADS) {
+ if (index >= 0 && index < uiInfo.q3HeadCount) {
+ return uiInfo.q3HeadIcons[index];
+ }
+ } else if (feederID == FEEDER_ALLMAPS || feederID == FEEDER_MAPS) {
+ int actual;
+ UI_SelectedMap(index, &actual);
+ index = actual;
+ if (index >= 0 && index < uiInfo.mapCount) {
+ if (uiInfo.mapList[index].levelShot == -1) {
+ uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[index].imageName);
+ }
+ return uiInfo.mapList[index].levelShot;
+ }
+ }
+ return 0;
+}
+
+static void UI_FeederSelection(float feederID, int index) {
+ static char info[MAX_STRING_CHARS];
+ if (feederID == FEEDER_HEADS) {
+ int actual;
+ UI_SelectedHead(index, &actual);
+ index = actual;
+ if (index >= 0 && index < uiInfo.characterCount) {
+ trap_Cvar_Set( "team_model", va("%s", uiInfo.characterList[index].base));
+ trap_Cvar_Set( "team_headmodel", va("*%s", uiInfo.characterList[index].name));
+ updateModel = qtrue;
+ }
+ } else if (feederID == FEEDER_Q3HEADS) {
+ if (index >= 0 && index < uiInfo.q3HeadCount) {
+ trap_Cvar_Set( "model", uiInfo.q3HeadNames[index]);
+ trap_Cvar_Set( "headmodel", uiInfo.q3HeadNames[index]);
+ updateModel = qtrue;
+ }
+ } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) {
+ int actual, map;
+ map = (feederID == FEEDER_ALLMAPS) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (uiInfo.mapList[map].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.mapList[map].cinematic);
+ uiInfo.mapList[map].cinematic = -1;
+ }
+ UI_SelectedMap(index, &actual);
+ trap_Cvar_Set("ui_mapIndex", va("%d", index));
+ ui_mapIndex.integer = index;
+
+ if (feederID == FEEDER_MAPS) {
+ ui_currentMap.integer = actual;
+ trap_Cvar_Set("ui_currentMap", va("%d", actual));
+ uiInfo.mapList[ui_currentMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);
+ trap_Cvar_Set("ui_opponentModel", uiInfo.mapList[ui_currentMap.integer].opponentName);
+ updateOpponentModel = qtrue;
+ } else {
+ ui_currentNetMap.integer = actual;
+ trap_Cvar_Set("ui_currentNetMap", va("%d", actual));
+ uiInfo.mapList[ui_currentNetMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+
+ } else if (feederID == FEEDER_SERVERS) {
+ const char *mapName = NULL;
+ uiInfo.serverStatus.currentServer = index;
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS);
+ uiInfo.serverStatus.currentServerPreview = trap_R_RegisterShaderNoMip(va("levelshots/%s", Info_ValueForKey(info, "mapname")));
+ if (uiInfo.serverStatus.currentServerCinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic);
+ uiInfo.serverStatus.currentServerCinematic = -1;
+ }
+ mapName = Info_ValueForKey(info, "mapname");
+ if (mapName && *mapName) {
+ uiInfo.serverStatus.currentServerCinematic = trap_CIN_PlayCinematic(va("%s.roq", mapName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+ } else if (feederID == FEEDER_SERVERSTATUS) {
+ //
+ } else if (feederID == FEEDER_FINDPLAYER) {
+ uiInfo.currentFoundPlayerServer = index;
+ //
+ if ( index < uiInfo.numFoundPlayerServers-1) {
+ // build a new server status for this server
+ Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress));
+ Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL);
+ UI_BuildServerStatus(qtrue);
+ }
+ } else if (feederID == FEEDER_PLAYER_LIST) {
+ uiInfo.playerIndex = index;
+ } else if (feederID == FEEDER_TEAM_LIST) {
+ uiInfo.teamIndex = index;
+ } else if (feederID == FEEDER_IGNORE_LIST) {
+ uiInfo.ignoreIndex = index;
+ } else if (feederID == FEEDER_MODS) {
+ uiInfo.modIndex = index;
+ } else if (feederID == FEEDER_CINEMATICS) {
+ uiInfo.movieIndex = index;
+ if (uiInfo.previewMovie >= 0) {
+ trap_CIN_StopCinematic(uiInfo.previewMovie);
+ }
+ uiInfo.previewMovie = -1;
+ } else if (feederID == FEEDER_DEMOS) {
+ uiInfo.demoIndex = index;
+ }
+
+//TA: tremulous menus
+ else if( feederID == FEEDER_TREMTEAMS )
+ uiInfo.tremTeamIndex = index;
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ uiInfo.tremHumanItemIndex = index;
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ uiInfo.tremAlienClassIndex = index;
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ uiInfo.tremHumanArmouryBuyIndex = index;
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ uiInfo.tremHumanArmourySellIndex = index;
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ uiInfo.tremAlienUpgradeIndex = index;
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ uiInfo.tremAlienBuildIndex = index;
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ uiInfo.tremHumanBuildIndex = index;
+//TA: tremulous menus
+}
+
+static void UI_Pause(qboolean b) {
+ if (b) {
+ // pause the game and set the ui keycatcher
+ trap_Cvar_Set( "cl_paused", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ } else {
+ // unpause the game and clear the ui keycatcher
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ }
+}
+
+static int UI_PlayCinematic(const char *name, float x, float y, float w, float h) {
+ return trap_CIN_PlayCinematic(name, x, y, w, h, (CIN_loop | CIN_silent));
+}
+
+static void UI_StopCinematic(int handle) {
+ if (handle >= 0) {
+ trap_CIN_StopCinematic(handle);
+ } else {
+ handle = abs(handle);
+ if (handle == UI_MAPCINEMATIC) {
+ if (uiInfo.mapList[ui_currentMap.integer].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.mapList[ui_currentMap.integer].cinematic);
+ uiInfo.mapList[ui_currentMap.integer].cinematic = -1;
+ }
+ } else if (handle == UI_NETMAPCINEMATIC) {
+ if (uiInfo.serverStatus.currentServerCinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic);
+ uiInfo.serverStatus.currentServerCinematic = -1;
+ }
+ } else if (handle == UI_CLANCINEMATIC) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ if (uiInfo.teamList[i].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic);
+ uiInfo.teamList[i].cinematic = -1;
+ }
+ }
+ }
+ }
+}
+
+static void UI_DrawCinematic(int handle, float x, float y, float w, float h) {
+ trap_CIN_SetExtents(handle, x, y, w, h);
+ trap_CIN_DrawCinematic(handle);
+}
+
+static void UI_RunCinematicFrame(int handle) {
+ trap_CIN_RunCinematic(handle);
+}
+
+
+/*
+=================
+UI_Init
+=================
+*/
+void _UI_Init( qboolean inGameLoad ) {
+ const char *menuSet;
+ int start;
+
+ BG_InitClassOverrides( );
+ BG_InitAllowedGameElements( );
+
+ //uiInfo.inGameLoad = inGameLoad;
+
+ UI_RegisterCvars();
+ UI_InitMemory();
+
+ // cache redundant calulations
+ trap_GetGlconfig( &uiInfo.uiDC.glconfig );
+
+ // for 640x480 virtualized screen
+ uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * (1.0/480.0);
+ uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * (1.0/640.0);
+ if ( uiInfo.uiDC.glconfig.vidWidth * 480 > uiInfo.uiDC.glconfig.vidHeight * 640 ) {
+ // wide screen
+ uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * (640.0/480.0) ) );
+ }
+ else {
+ // no wide screen
+ uiInfo.uiDC.bias = 0;
+ }
+
+
+ //UI_Load();
+ uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip;
+ uiInfo.uiDC.setColor = &UI_SetColor;
+ uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic;
+ uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic;
+ uiInfo.uiDC.drawText = &Text_Paint;
+ uiInfo.uiDC.textWidth = &Text_Width;
+ uiInfo.uiDC.textHeight = &Text_Height;
+ uiInfo.uiDC.registerModel = &trap_R_RegisterModel;
+ uiInfo.uiDC.modelBounds = &trap_R_ModelBounds;
+ uiInfo.uiDC.fillRect = &UI_FillRect;
+ uiInfo.uiDC.drawRect = &_UI_DrawRect;
+ uiInfo.uiDC.drawSides = &_UI_DrawSides;
+ uiInfo.uiDC.drawTopBottom = &_UI_DrawTopBottom;
+ uiInfo.uiDC.clearScene = &trap_R_ClearScene;
+ uiInfo.uiDC.drawSides = &_UI_DrawSides;
+ uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene;
+ uiInfo.uiDC.renderScene = &trap_R_RenderScene;
+ uiInfo.uiDC.registerFont = &trap_R_RegisterFont;
+ uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw;
+ uiInfo.uiDC.getValue = &UI_GetValue;
+ uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible;
+ uiInfo.uiDC.runScript = &UI_RunMenuScript;
+ uiInfo.uiDC.getTeamColor = &UI_GetTeamColor;
+ uiInfo.uiDC.setCVar = trap_Cvar_Set;
+ uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer;
+ uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue;
+ uiInfo.uiDC.drawTextWithCursor = &Text_PaintWithCursor;
+ uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode;
+ uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode;
+ uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound;
+ uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey;
+ uiInfo.uiDC.feederCount = &UI_FeederCount;
+ uiInfo.uiDC.feederItemImage = &UI_FeederItemImage;
+ uiInfo.uiDC.feederItemText = &UI_FeederItemText;
+ uiInfo.uiDC.feederSelection = &UI_FeederSelection;
+ uiInfo.uiDC.setBinding = &trap_Key_SetBinding;
+ uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf;
+ uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf;
+ uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText;
+ uiInfo.uiDC.Error = &Com_Error;
+ uiInfo.uiDC.Print = &Com_Printf;
+ uiInfo.uiDC.Pause = &UI_Pause;
+ uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth;
+ uiInfo.uiDC.registerSound = &trap_S_RegisterSound;
+ uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack;
+ uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack;
+ uiInfo.uiDC.playCinematic = &UI_PlayCinematic;
+ uiInfo.uiDC.stopCinematic = &UI_StopCinematic;
+ uiInfo.uiDC.drawCinematic = &UI_DrawCinematic;
+ uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame;
+
+ Init_Display(&uiInfo.uiDC);
+
+ String_Init();
+
+ uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" );
+
+ AssetCache();
+
+ start = trap_Milliseconds();
+
+ uiInfo.teamCount = 0;
+ uiInfo.characterCount = 0;
+ uiInfo.aliasCount = 0;
+
+/* UI_ParseTeamInfo("teaminfo.txt");
+ UI_LoadTeams();
+ UI_ParseGameInfo("gameinfo.txt");*/
+
+ menuSet = UI_Cvar_VariableString("ui_menuFiles");
+ if (menuSet == NULL || menuSet[0] == '\0') {
+ menuSet = "ui/menus.txt";
+ }
+
+#if 0
+ if (uiInfo.inGameLoad) {
+ UI_LoadMenus("ui/ingame.txt", qtrue);
+ } else { // bk010222: left this: UI_LoadMenus(menuSet, qtrue);
+ }
+#else
+ UI_LoadMenus(menuSet, qtrue);
+ UI_LoadMenus("ui/ingame.txt", qfalse);
+ UI_LoadMenus("ui/tremulous.txt", qfalse);
+
+ UI_LoadInfoPanes( "ui/infopanes.def" );
+
+ if( uiInfo.uiDC.debug )
+ {
+ int i, j;
+
+ for( i = 0; i < uiInfo.tremInfoPaneCount; i++ )
+ {
+ Com_Printf( "name: %s\n", uiInfo.tremInfoPanes[ i ].name );
+
+ Com_Printf( "text: %s\n", uiInfo.tremInfoPanes[ i ].text );
+
+ for( j = 0; j < uiInfo.tremInfoPanes[ i ].numGraphics; j++ )
+ Com_Printf( "graphic %d: %d %d %d %d\n", j, uiInfo.tremInfoPanes[ i ].graphics[ j ].side,
+ uiInfo.tremInfoPanes[ i ].graphics[ j ].offset,
+ uiInfo.tremInfoPanes[ i ].graphics[ j ].width,
+ uiInfo.tremInfoPanes[ i ].graphics[ j ].height );
+ }
+ }
+#endif
+
+ Menus_CloseAll();
+
+ trap_LAN_LoadCachedServers();
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);
+
+ // sets defaults for ui temp cvars
+ uiInfo.effectsColor = gamecodetoui[(int)trap_Cvar_VariableValue("color1")-1];
+ uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair");
+ trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1");
+
+ uiInfo.serverStatus.currentServerCinematic = -1;
+ uiInfo.previewMovie = -1;
+
+ if (trap_Cvar_VariableValue("ui_TeamArenaFirstRun") == 0) {
+ trap_Cvar_Set("s_volume", "0.8");
+ trap_Cvar_Set("s_musicvolume", "0.5");
+ trap_Cvar_Set("ui_TeamArenaFirstRun", "1");
+ }
+
+ trap_Cvar_Register(NULL, "debug_protocol", "", 0 );
+
+ trap_Cvar_Set("ui_actualNetGameType", va("%d", ui_netGameType.integer));
+}
+
+
+/*
+=================
+UI_KeyEvent
+=================
+*/
+void _UI_KeyEvent( int key, qboolean down ) {
+
+ if (Menu_Count() > 0) {
+ menuDef_t *menu = Menu_GetFocused();
+ if (menu) {
+ if (key == K_ESCAPE && down && !Menus_AnyFullScreenVisible()) {
+ Menus_CloseAll();
+ } else {
+ Menu_HandleKey(menu, key, down );
+ }
+ } else {
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ }
+ }
+
+ //if ((s > 0) && (s != menu_null_sound)) {
+ // trap_S_StartLocalSound( s, CHAN_LOCAL_SOUND );
+ //}
+}
+
+/*
+=================
+UI_MouseEvent
+=================
+*/
+void _UI_MouseEvent( int dx, int dy )
+{
+ // update mouse screen position
+ uiInfo.uiDC.cursorx += dx;
+ if (uiInfo.uiDC.cursorx < 0)
+ uiInfo.uiDC.cursorx = 0;
+ else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH)
+ uiInfo.uiDC.cursorx = SCREEN_WIDTH;
+
+ uiInfo.uiDC.cursory += dy;
+ if (uiInfo.uiDC.cursory < 0)
+ uiInfo.uiDC.cursory = 0;
+ else if (uiInfo.uiDC.cursory > SCREEN_HEIGHT)
+ uiInfo.uiDC.cursory = SCREEN_HEIGHT;
+
+ if (Menu_Count() > 0) {
+ //menuDef_t *menu = Menu_GetFocused();
+ //Menu_HandleMouseMove(menu, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory);
+ Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory);
+ }
+
+}
+
+/*
+=================
+UI_MousePosition
+=================
+*/
+int _UI_MousePosition( void )
+{
+ return (int)rint( uiInfo.uiDC.cursorx ) |
+ (int)rint( uiInfo.uiDC.cursory ) << 16;
+}
+
+/*
+=================
+UI_SetMousePosition
+=================
+*/
+void _UI_SetMousePosition( int x, int y )
+{
+ uiInfo.uiDC.cursorx = x;
+ uiInfo.uiDC.cursory = y;
+
+ if( Menu_Count( ) > 0 )
+ Display_MouseMove( NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory );
+}
+
+void UI_LoadNonIngame( void ) {
+ const char *menuSet = UI_Cvar_VariableString("ui_menuFiles");
+ if (menuSet == NULL || menuSet[0] == '\0') {
+ menuSet = "ui/menus.txt";
+ }
+ UI_LoadMenus(menuSet, qfalse);
+ uiInfo.inGameLoad = qfalse;
+}
+
+void _UI_SetActiveMenu( uiMenuCommand_t menu ) {
+ char buf[256];
+
+ // this should be the ONLY way the menu system is brought up
+ // enusure minumum menu data is cached
+ if (Menu_Count() > 0) {
+ vec3_t v;
+ v[0] = v[1] = v[2] = 0;
+ switch ( menu ) {
+ case UIMENU_NONE:
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+
+ return;
+ case UIMENU_MAIN:
+ //trap_Cvar_Set( "sv_killserver", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ //trap_S_StartLocalSound( trap_S_RegisterSound("sound/misc/menu_background.wav", qfalse) , CHAN_LOCAL_SOUND );
+ //trap_S_StartBackgroundTrack("sound/misc/menu_background.wav", NULL);
+ if (uiInfo.inGameLoad) {
+ UI_LoadNonIngame();
+ }
+ Menus_CloseAll();
+ Menus_ActivateByName("main");
+ trap_Cvar_Set( "ui_loading", "0" );
+ trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf));
+ if (strlen(buf)) {
+ if (!ui_singlePlayerActive.integer) {
+ if( trap_Cvar_VariableValue( "com_errorCode" ) == ERR_SERVERDISCONNECT )
+ Menus_ActivateByName("drop_popmenu");
+ else
+ Menus_ActivateByName("error_popmenu");
+ } else {
+ trap_Cvar_Set("com_errorMessage", "");
+ }
+ }
+ return;
+ case UIMENU_TEAM:
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_ActivateByName("team");
+ return;
+ case UIMENU_POSTGAME:
+ //trap_Cvar_Set( "sv_killserver", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ if (uiInfo.inGameLoad) {
+ UI_LoadNonIngame();
+ }
+ Menus_CloseAll();
+ Menus_ActivateByName("endofgame");
+ return;
+ case UIMENU_INGAME:
+ trap_Cvar_Set( "cl_paused", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ UI_BuildPlayerList();
+ Menus_CloseAll();
+ Menus_ActivateByName("ingame");
+ return;
+ }
+ }
+}
+
+qboolean _UI_IsFullscreen( void ) {
+ return Menus_AnyFullScreenVisible();
+}
+
+
+
+static connstate_t lastConnState;
+static char lastLoadingText[MAX_INFO_VALUE];
+
+static void UI_ReadableSize ( char *buf, int bufsize, int value )
+{
+ if (value > 1024*1024*1024 ) { // gigs
+ Com_sprintf( buf, bufsize, "%d", value / (1024*1024*1024) );
+ Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d GB",
+ (value % (1024*1024*1024))*100 / (1024*1024*1024) );
+ } else if (value > 1024*1024 ) { // megs
+ Com_sprintf( buf, bufsize, "%d", value / (1024*1024) );
+ Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d MB",
+ (value % (1024*1024))*100 / (1024*1024) );
+ } else if (value > 1024 ) { // kilos
+ Com_sprintf( buf, bufsize, "%d KB", value / 1024 );
+ } else { // bytes
+ Com_sprintf( buf, bufsize, "%d bytes", value );
+ }
+}
+
+// Assumes time is in msec
+static void UI_PrintTime ( char *buf, int bufsize, int time ) {
+ time /= 1000; // change to seconds
+
+ if (time > 3600) { // in the hours range
+ Com_sprintf( buf, bufsize, "%d hr %d min", time / 3600, (time % 3600) / 60 );
+ } else if (time > 60) { // mins
+ Com_sprintf( buf, bufsize, "%d min %d sec", time / 60, time % 60 );
+ } else { // secs
+ Com_sprintf( buf, bufsize, "%d sec", time );
+ }
+}
+
+void Text_PaintCenter(float x, float y, float scale, vec4_t color, const char *text, float adjust) {
+ int len = Text_Width(text, scale, 0);
+ Text_Paint(x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
+}
+
+void Text_PaintCenter_AutoWrapped(float x, float y, float xmax, float ystep, float scale, vec4_t color, const char *str, float adjust) {
+ int width;
+ char *s1,*s2,*s3;
+ char c_bcp;
+ char buf[1024];
+
+ if (!str || str[0]=='\0')
+ return;
+
+ Q_strncpyz(buf, str, sizeof(buf));
+ s1 = s2 = s3 = buf;
+
+ while (1) {
+ do {
+ s3++;
+ } while (*s3!=' ' && *s3!='\0');
+ c_bcp = *s3;
+ *s3 = '\0';
+ width = Text_Width(s1, scale, 0);
+ *s3 = c_bcp;
+ if (width > xmax) {
+ if (s1==s2)
+ {
+ // fuck, don't have a clean cut, we'll overflow
+ s2 = s3;
+ }
+ *s2 = '\0';
+ Text_PaintCenter(x, y, scale, color, s1, adjust);
+ y += ystep;
+ if (c_bcp == '\0')
+ {
+ // that was the last word
+ // we could start a new loop, but that wouldn't be much use
+ // even if the word is too long, we would overflow it (see above)
+ // so just print it now if needed
+ s2++;
+ if (*s2 != '\0') // if we are printing an overflowing line we have s2 == s3
+ Text_PaintCenter(x, y, scale, color, s2, adjust);
+ break;
+ }
+ s2++;
+ s1 = s2;
+ s3 = s2;
+ }
+ else
+ {
+ s2 = s3;
+ if (c_bcp == '\0') // we reached the end
+ {
+ Text_PaintCenter(x, y, scale, color, s1, adjust);
+ break;
+ }
+ }
+ }
+}
+
+
+static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale ) {
+ static char dlText[] = "Downloading:";
+ static char etaText[] = "Estimated time left:";
+ static char xferText[] = "Transfer rate:";
+
+ int downloadSize, downloadCount, downloadTime;
+ char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64];
+ int xferRate;
+ int leftWidth;
+ const char *s;
+
+ downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" );
+ downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" );
+ downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" );
+
+ leftWidth = 320;
+
+ UI_SetColor(colorWhite);
+ Text_PaintCenter(centerPoint, yStart + 112, scale, colorWhite, dlText, 0);
+ Text_PaintCenter(centerPoint, yStart + 192, scale, colorWhite, etaText, 0);
+ Text_PaintCenter(centerPoint, yStart + 248, scale, colorWhite, xferText, 0);
+
+ if (downloadSize > 0) {
+ s = va( "%s (%d%%)", downloadName, downloadCount * 100 / downloadSize );
+ } else {
+ s = downloadName;
+ }
+
+ Text_PaintCenter(centerPoint, yStart+136, scale, colorWhite, s, 0);
+
+ UI_ReadableSize( dlSizeBuf, sizeof dlSizeBuf, downloadCount );
+ UI_ReadableSize( totalSizeBuf, sizeof totalSizeBuf, downloadSize );
+
+ if (downloadCount < 4096 || !downloadTime) {
+ Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0);
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0);
+ } else {
+ if ((uiInfo.uiDC.realTime - downloadTime) / 1000) {
+ xferRate = downloadCount / ((uiInfo.uiDC.realTime - downloadTime) / 1000);
+ } else {
+ xferRate = 0;
+ }
+ UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate );
+
+ // Extrapolate estimated completion time
+ if (downloadSize && xferRate) {
+ int n = downloadSize / xferRate; // estimated time for entire d/l in secs
+
+ // We do it in K (/1024) because we'd overflow around 4MB
+ UI_PrintTime ( dlTimeBuf, sizeof dlTimeBuf,
+ (n - (((downloadCount/1024) * n) / (downloadSize/1024))) * 1000);
+
+ Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, dlTimeBuf, 0);
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0);
+ } else {
+ Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0);
+ if (downloadSize) {
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0);
+ } else {
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s copied)", dlSizeBuf), 0);
+ }
+ }
+
+ if (xferRate) {
+ Text_PaintCenter(leftWidth, yStart+272, scale, colorWhite, va("%s/Sec", xferRateBuf), 0);
+ }
+ }
+}
+
+/*
+========================
+UI_DrawConnectScreen
+
+This will also be overlaid on the cgame info screen during loading
+to prevent it from blinking away too rapidly on local or lan games.
+========================
+*/
+void UI_DrawConnectScreen( qboolean overlay ) {
+ char *s;
+ uiClientState_t cstate;
+ char info[MAX_INFO_VALUE];
+ char text[256];
+ float centerPoint, yStart, scale;
+
+ menuDef_t *menu = Menus_FindByName("Connect");
+
+
+ if ( !overlay && menu ) {
+ Menu_Paint(menu, qtrue);
+ }
+
+ if (!overlay) {
+ centerPoint = 320;
+ yStart = 130;
+ scale = 0.5f;
+ } else {
+ centerPoint = 320;
+ yStart = 32;
+ scale = 0.6f;
+ return;
+ }
+
+ // see what information we should display
+ trap_GetClientState( &cstate );
+
+ info[0] = '\0';
+ if( trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ) ) {
+ Text_PaintCenter(centerPoint, yStart, scale, colorWhite, va( "Loading %s", Info_ValueForKey( info, "mapname" )), 0);
+ }
+
+ if (!Q_stricmp(cstate.servername,"localhost")) {
+ Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite, va("Starting up..."), ITEM_TEXTSTYLE_SHADOWEDMORE);
+ } else {
+ strcpy(text, va("Connecting to %s", cstate.servername));
+ Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite,text , ITEM_TEXTSTYLE_SHADOWEDMORE);
+ }
+
+
+ // display global MOTD at bottom
+ Text_PaintCenter(centerPoint, 600, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0);
+ // print any server info (server full, bad version, etc)
+ if ( cstate.connState < CA_CONNECTED ) {
+ Text_PaintCenter_AutoWrapped(centerPoint, yStart + 176, 630, 20, scale, colorWhite, cstate.messageString, 0);
+ }
+
+ if ( lastConnState > cstate.connState ) {
+ lastLoadingText[0] = '\0';
+ }
+ lastConnState = cstate.connState;
+
+ switch ( cstate.connState ) {
+ case CA_CONNECTING:
+ s = va("Awaiting connection...%i", cstate.connectPacketCount);
+ break;
+ case CA_CHALLENGING:
+ s = va("Awaiting challenge...%i", cstate.connectPacketCount);
+ break;
+ case CA_CONNECTED: {
+ char downloadName[MAX_INFO_VALUE];
+
+ trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof(downloadName) );
+ if (*downloadName) {
+ UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale );
+ return;
+ }
+ }
+ s = "Awaiting gamestate...";
+ break;
+ case CA_LOADING:
+ return;
+ case CA_PRIMED:
+ return;
+ default:
+ return;
+ }
+
+
+ if (Q_stricmp(cstate.servername,"localhost")) {
+ Text_PaintCenter(centerPoint, yStart + 80, scale, colorWhite, s, 0);
+ }
+
+ // password required / connection rejected information goes here
+}
+
+
+/*
+================
+cvars
+================
+*/
+
+typedef struct {
+ vmCvar_t *vmCvar;
+ char *cvarName;
+ char *defaultString;
+ int cvarFlags;
+} cvarTable_t;
+
+vmCvar_t ui_ffa_fraglimit;
+vmCvar_t ui_ffa_timelimit;
+
+vmCvar_t ui_tourney_fraglimit;
+vmCvar_t ui_tourney_timelimit;
+
+vmCvar_t ui_team_fraglimit;
+vmCvar_t ui_team_timelimit;
+vmCvar_t ui_team_friendly;
+
+vmCvar_t ui_ctf_capturelimit;
+vmCvar_t ui_ctf_timelimit;
+vmCvar_t ui_ctf_friendly;
+
+vmCvar_t ui_arenasFile;
+vmCvar_t ui_botsFile;
+vmCvar_t ui_spScores1;
+vmCvar_t ui_spScores2;
+vmCvar_t ui_spScores3;
+vmCvar_t ui_spScores4;
+vmCvar_t ui_spScores5;
+vmCvar_t ui_spAwards;
+vmCvar_t ui_spVideos;
+vmCvar_t ui_spSkill;
+
+vmCvar_t ui_spSelection;
+
+vmCvar_t ui_browserMaster;
+vmCvar_t ui_browserGameType;
+vmCvar_t ui_browserSortKey;
+vmCvar_t ui_browserShowFull;
+vmCvar_t ui_browserShowEmpty;
+
+vmCvar_t ui_brassTime;
+vmCvar_t ui_drawCrosshair;
+vmCvar_t ui_drawCrosshairNames;
+vmCvar_t ui_marks;
+
+vmCvar_t ui_server1;
+vmCvar_t ui_server2;
+vmCvar_t ui_server3;
+vmCvar_t ui_server4;
+vmCvar_t ui_server5;
+vmCvar_t ui_server6;
+vmCvar_t ui_server7;
+vmCvar_t ui_server8;
+vmCvar_t ui_server9;
+vmCvar_t ui_server10;
+vmCvar_t ui_server11;
+vmCvar_t ui_server12;
+vmCvar_t ui_server13;
+vmCvar_t ui_server14;
+vmCvar_t ui_server15;
+vmCvar_t ui_server16;
+
+vmCvar_t ui_redteam;
+vmCvar_t ui_redteam1;
+vmCvar_t ui_redteam2;
+vmCvar_t ui_redteam3;
+vmCvar_t ui_redteam4;
+vmCvar_t ui_redteam5;
+vmCvar_t ui_blueteam;
+vmCvar_t ui_blueteam1;
+vmCvar_t ui_blueteam2;
+vmCvar_t ui_blueteam3;
+vmCvar_t ui_blueteam4;
+vmCvar_t ui_blueteam5;
+vmCvar_t ui_teamName;
+vmCvar_t ui_dedicated;
+vmCvar_t ui_gameType;
+vmCvar_t ui_netGameType;
+vmCvar_t ui_actualNetGameType;
+vmCvar_t ui_joinGameType;
+vmCvar_t ui_netSource;
+vmCvar_t ui_serverFilterType;
+vmCvar_t ui_opponentName;
+vmCvar_t ui_menuFiles;
+vmCvar_t ui_currentTier;
+vmCvar_t ui_currentMap;
+vmCvar_t ui_currentNetMap;
+vmCvar_t ui_mapIndex;
+vmCvar_t ui_currentOpponent;
+vmCvar_t ui_selectedPlayer;
+vmCvar_t ui_selectedPlayerName;
+vmCvar_t ui_lastServerRefresh_0;
+vmCvar_t ui_lastServerRefresh_1;
+vmCvar_t ui_lastServerRefresh_2;
+vmCvar_t ui_lastServerRefresh_3;
+vmCvar_t ui_lastServerRefresh_0_time;
+vmCvar_t ui_lastServerRefresh_1_time;
+vmCvar_t ui_lastServerRefresh_2_time;
+vmCvar_t ui_lastServerRefresh_3_time;
+vmCvar_t ui_singlePlayerActive;
+vmCvar_t ui_scoreAccuracy;
+vmCvar_t ui_scoreImpressives;
+vmCvar_t ui_scoreExcellents;
+vmCvar_t ui_scoreCaptures;
+vmCvar_t ui_scoreDefends;
+vmCvar_t ui_scoreAssists;
+vmCvar_t ui_scoreGauntlets;
+vmCvar_t ui_scoreScore;
+vmCvar_t ui_scorePerfect;
+vmCvar_t ui_scoreTeam;
+vmCvar_t ui_scoreBase;
+vmCvar_t ui_scoreTimeBonus;
+vmCvar_t ui_scoreSkillBonus;
+vmCvar_t ui_scoreShutoutBonus;
+vmCvar_t ui_scoreTime;
+vmCvar_t ui_captureLimit;
+vmCvar_t ui_fragLimit;
+vmCvar_t ui_smallFont;
+vmCvar_t ui_bigFont;
+vmCvar_t ui_findPlayer;
+vmCvar_t ui_Q3Model;
+vmCvar_t ui_hudFiles;
+vmCvar_t ui_recordSPDemo;
+vmCvar_t ui_realCaptureLimit;
+vmCvar_t ui_realWarmUp;
+vmCvar_t ui_serverStatusTimeOut;
+
+//TA: bank values
+vmCvar_t ui_bank;
+vmCvar_t ui_winner;
+
+vmCvar_t ui_chatCommands;
+
+
+// bk001129 - made static to avoid aliasing
+static cvarTable_t cvarTable[] = {
+ { &ui_ffa_fraglimit, "ui_ffa_fraglimit", "20", CVAR_ARCHIVE },
+ { &ui_ffa_timelimit, "ui_ffa_timelimit", "0", CVAR_ARCHIVE },
+
+ { &ui_tourney_fraglimit, "ui_tourney_fraglimit", "0", CVAR_ARCHIVE },
+ { &ui_tourney_timelimit, "ui_tourney_timelimit", "15", CVAR_ARCHIVE },
+
+ { &ui_team_fraglimit, "ui_team_fraglimit", "0", CVAR_ARCHIVE },
+ { &ui_team_timelimit, "ui_team_timelimit", "20", CVAR_ARCHIVE },
+ { &ui_team_friendly, "ui_team_friendly", "1", CVAR_ARCHIVE },
+
+ { &ui_ctf_capturelimit, "ui_ctf_capturelimit", "8", CVAR_ARCHIVE },
+ { &ui_ctf_timelimit, "ui_ctf_timelimit", "30", CVAR_ARCHIVE },
+ { &ui_ctf_friendly, "ui_ctf_friendly", "0", CVAR_ARCHIVE },
+
+ { &ui_arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM },
+ { &ui_botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM },
+ { &ui_spScores1, "g_spScores1", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores2, "g_spScores2", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores3, "g_spScores3", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores4, "g_spScores4", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores5, "g_spScores5", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spAwards, "g_spAwards", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spVideos, "g_spVideos", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spSkill, "g_spSkill", "2", CVAR_ARCHIVE },
+
+ { &ui_spSelection, "ui_spSelection", "", CVAR_ROM },
+ { &ui_winner, "ui_winner", "", CVAR_ROM },
+
+ { &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE },
+ { &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE },
+ { &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE },
+ { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE },
+ { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE },
+
+ { &ui_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE },
+ { &ui_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE },
+ { &ui_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE },
+ { &ui_marks, "cg_marks", "1", CVAR_ARCHIVE },
+
+ { &ui_server1, "server1", "", CVAR_ARCHIVE },
+ { &ui_server2, "server2", "", CVAR_ARCHIVE },
+ { &ui_server3, "server3", "", CVAR_ARCHIVE },
+ { &ui_server4, "server4", "", CVAR_ARCHIVE },
+ { &ui_server5, "server5", "", CVAR_ARCHIVE },
+ { &ui_server6, "server6", "", CVAR_ARCHIVE },
+ { &ui_server7, "server7", "", CVAR_ARCHIVE },
+ { &ui_server8, "server8", "", CVAR_ARCHIVE },
+ { &ui_server9, "server9", "", CVAR_ARCHIVE },
+ { &ui_server10, "server10", "", CVAR_ARCHIVE },
+ { &ui_server11, "server11", "", CVAR_ARCHIVE },
+ { &ui_server12, "server12", "", CVAR_ARCHIVE },
+ { &ui_server13, "server13", "", CVAR_ARCHIVE },
+ { &ui_server14, "server14", "", CVAR_ARCHIVE },
+ { &ui_server15, "server15", "", CVAR_ARCHIVE },
+ { &ui_server16, "server16", "", CVAR_ARCHIVE },
+ { &ui_new, "ui_new", "0", CVAR_TEMP },
+ { &ui_debug, "ui_debug", "0", CVAR_TEMP },
+ { &ui_initialized, "ui_initialized", "0", CVAR_TEMP },
+ { &ui_teamName, "ui_teamName", "Pagans", CVAR_ARCHIVE },
+ { &ui_opponentName, "ui_opponentName", "Stroggs", CVAR_ARCHIVE },
+ { &ui_redteam, "ui_redteam", "Pagans", CVAR_ARCHIVE },
+ { &ui_blueteam, "ui_blueteam", "Stroggs", CVAR_ARCHIVE },
+ { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE },
+ { &ui_gameType, "ui_gametype", "3", CVAR_ARCHIVE },
+ { &ui_joinGameType, "ui_joinGametype", "0", CVAR_ARCHIVE },
+ { &ui_netGameType, "ui_netGametype", "3", CVAR_ARCHIVE },
+ { &ui_actualNetGameType, "ui_actualNetGametype", "3", CVAR_ARCHIVE },
+ { &ui_redteam1, "ui_redteam1", "0", CVAR_ARCHIVE },
+ { &ui_redteam2, "ui_redteam2", "0", CVAR_ARCHIVE },
+ { &ui_redteam3, "ui_redteam3", "0", CVAR_ARCHIVE },
+ { &ui_redteam4, "ui_redteam4", "0", CVAR_ARCHIVE },
+ { &ui_redteam5, "ui_redteam5", "0", CVAR_ARCHIVE },
+ { &ui_blueteam1, "ui_blueteam1", "0", CVAR_ARCHIVE },
+ { &ui_blueteam2, "ui_blueteam2", "0", CVAR_ARCHIVE },
+ { &ui_blueteam3, "ui_blueteam3", "0", CVAR_ARCHIVE },
+ { &ui_blueteam4, "ui_blueteam4", "0", CVAR_ARCHIVE },
+ { &ui_blueteam5, "ui_blueteam5", "0", CVAR_ARCHIVE },
+ { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE },
+ { &ui_menuFiles, "ui_menuFiles", "ui/menus.txt", CVAR_ARCHIVE },
+ { &ui_currentTier, "ui_currentTier", "0", CVAR_ARCHIVE },
+ { &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE },
+ { &ui_currentNetMap, "ui_currentNetMap", "0", CVAR_ARCHIVE },
+ { &ui_mapIndex, "ui_mapIndex", "0", CVAR_ARCHIVE },
+ { &ui_currentOpponent, "ui_currentOpponent", "0", CVAR_ARCHIVE },
+ { &ui_selectedPlayer, "cg_selectedPlayer", "0", CVAR_ARCHIVE},
+ { &ui_selectedPlayerName, "cg_selectedPlayerName", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3_time", "", CVAR_ARCHIVE},
+ { &ui_singlePlayerActive, "ui_singlePlayerActive", "0", 0},
+ { &ui_scoreAccuracy, "ui_scoreAccuracy", "0", CVAR_ARCHIVE},
+ { &ui_scoreImpressives, "ui_scoreImpressives", "0", CVAR_ARCHIVE},
+ { &ui_scoreExcellents, "ui_scoreExcellents", "0", CVAR_ARCHIVE},
+ { &ui_scoreCaptures, "ui_scoreCaptures", "0", CVAR_ARCHIVE},
+ { &ui_scoreDefends, "ui_scoreDefends", "0", CVAR_ARCHIVE},
+ { &ui_scoreAssists, "ui_scoreAssists", "0", CVAR_ARCHIVE},
+ { &ui_scoreGauntlets, "ui_scoreGauntlets", "0",CVAR_ARCHIVE},
+ { &ui_scoreScore, "ui_scoreScore", "0", CVAR_ARCHIVE},
+ { &ui_scorePerfect, "ui_scorePerfect", "0", CVAR_ARCHIVE},
+ { &ui_scoreTeam, "ui_scoreTeam", "0 to 0", CVAR_ARCHIVE},
+ { &ui_scoreBase, "ui_scoreBase", "0", CVAR_ARCHIVE},
+ { &ui_scoreTime, "ui_scoreTime", "00:00", CVAR_ARCHIVE},
+ { &ui_scoreTimeBonus, "ui_scoreTimeBonus", "0", CVAR_ARCHIVE},
+ { &ui_scoreSkillBonus, "ui_scoreSkillBonus", "0", CVAR_ARCHIVE},
+ { &ui_scoreShutoutBonus, "ui_scoreShutoutBonus", "0", CVAR_ARCHIVE},
+ { &ui_fragLimit, "ui_fragLimit", "10", 0},
+ { &ui_captureLimit, "ui_captureLimit", "5", 0},
+ { &ui_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE},
+ { &ui_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE},
+ { &ui_findPlayer, "ui_findPlayer", "Sarge", CVAR_ARCHIVE},
+ { &ui_Q3Model, "ui_q3model", "0", CVAR_ARCHIVE},
+ { &ui_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE},
+ { &ui_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE},
+ { &ui_teamArenaFirstRun, "ui_teamArenaFirstRun", "0", CVAR_ARCHIVE},
+ { &ui_realWarmUp, "g_warmup", "20", CVAR_ARCHIVE},
+ { &ui_realCaptureLimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART},
+ { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE},
+
+ { &ui_bank, "ui_bank", "0", 0 },
+
+ { &ui_chatCommands, "ui_chatCommands", "1", CVAR_ARCHIVE},
+};
+
+// bk001129 - made static to avoid aliasing
+static int cvarTableSize = sizeof(cvarTable) / sizeof(cvarTable[0]);
+
+
+/*
+=================
+UI_RegisterCvars
+=================
+*/
+void UI_RegisterCvars( void ) {
+ int i;
+ cvarTable_t *cv;
+
+ for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) {
+ trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags );
+ }
+}
+
+/*
+=================
+UI_UpdateCvars
+=================
+*/
+void UI_UpdateCvars( void ) {
+ int i;
+ cvarTable_t *cv;
+
+ for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) {
+ trap_Cvar_Update( cv->vmCvar );
+ }
+}
+
+
+/*
+=================
+ArenaServers_StopRefresh
+=================
+*/
+static void UI_StopServerRefresh( void )
+{
+ int count;
+
+ if (!uiInfo.serverStatus.refreshActive) {
+ // not currently refreshing
+ return;
+ }
+ uiInfo.serverStatus.refreshActive = qfalse;
+ Com_Printf("%d servers listed in browser with %d players.\n",
+ uiInfo.serverStatus.numDisplayServers,
+ uiInfo.serverStatus.numPlayersOnServers);
+ count = trap_LAN_GetServerCount(ui_netSource.integer);
+ if (count - uiInfo.serverStatus.numDisplayServers > 0) {
+ Com_Printf("%d servers not listed due to packet loss or pings higher than %d\n",
+ count - uiInfo.serverStatus.numDisplayServers,
+ (int) trap_Cvar_VariableValue("cl_maxPing"));
+ }
+
+}
+
+/*
+=================
+UI_DoServerRefresh
+=================
+*/
+static void UI_DoServerRefresh( void )
+{
+ qboolean wait = qfalse;
+
+ if (!uiInfo.serverStatus.refreshActive) {
+ return;
+ }
+ if (ui_netSource.integer != AS_FAVORITES) {
+ if (ui_netSource.integer == AS_LOCAL) {
+ if (!trap_LAN_GetServerCount(ui_netSource.integer)) {
+ wait = qtrue;
+ }
+ } else {
+ if (trap_LAN_GetServerCount(ui_netSource.integer) < 0) {
+ wait = qtrue;
+ }
+ }
+ }
+
+ if (uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime) {
+ if (wait) {
+ return;
+ }
+ }
+
+ // if still trying to retrieve pings
+ if (trap_LAN_UpdateVisiblePings(ui_netSource.integer)) {
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+ } else if (!wait) {
+ // get the last servers in the list
+ UI_BuildServerDisplayList(2);
+ // stop the refresh
+ UI_StopServerRefresh();
+ }
+ //
+ UI_BuildServerDisplayList(qfalse);
+}
+
+/*
+=================
+UI_StartServerRefresh
+=================
+*/
+static void UI_StartServerRefresh(qboolean full)
+{
+ int i;
+ char *ptr;
+ int time;
+ qtime_t q;
+
+ time = trap_RealTime(&q);
+ trap_Cvar_Set( va("ui_lastServerRefresh_%i_time", ui_netSource.integer ),
+ va( "%i", time ) );
+ trap_Cvar_Set( va("ui_lastServerRefresh_%i", ui_netSource.integer),
+ va("%s-%i, %i at %i:%02i", MonthAbbrev[q.tm_mon],q.tm_mday, 1900+q.tm_year,q.tm_hour,q.tm_min));
+
+ if (!full) {
+ UI_UpdatePendingPings();
+ return;
+ }
+
+ uiInfo.serverStatus.refreshActive = qtrue;
+ uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000;
+ // clear number of displayed servers
+ uiInfo.serverStatus.numDisplayServers = 0;
+ uiInfo.serverStatus.numPlayersOnServers = 0;
+ // mark all servers as visible so we store ping updates for them
+ trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue);
+ // reset all the pings
+ trap_LAN_ResetPings(ui_netSource.integer);
+ //
+ if( ui_netSource.integer == AS_LOCAL ) {
+ trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" );
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+ return;
+ }
+
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000;
+ if( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER ) {
+ if( ui_netSource.integer == AS_GLOBAL ) {
+ i = 0;
+ }
+ else {
+ i = 1;
+ }
+
+ ptr = UI_Cvar_VariableString("debug_protocol");
+ if (strlen(ptr)) {
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s full empty\n", i, ptr));
+ }
+ else {
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d full empty\n", i, (int)trap_Cvar_VariableValue( "protocol" ) ) );
+ }
+ }
+}
+
diff --git a/src/ui/ui_players.c b/src/ui/ui_players.c
new file mode 100644
index 0000000..55fce51
--- /dev/null
+++ b/src/ui/ui_players.c
@@ -0,0 +1,1369 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// ui_players.c
+
+#include "ui_local.h"
+
+
+#define UI_TIMER_GESTURE 2300
+#define UI_TIMER_JUMP 1000
+#define UI_TIMER_LAND 130
+#define UI_TIMER_WEAPON_SWITCH 300
+#define UI_TIMER_ATTACK 500
+#define UI_TIMER_MUZZLE_FLASH 20
+#define UI_TIMER_WEAPON_DELAY 250
+
+#define JUMP_HEIGHT 56
+
+#define SWINGSPEED 0.3f
+
+#define SPIN_SPEED 0.9f
+#define COAST_TIME 1000
+
+
+static int dp_realtime;
+static float jumpHeight;
+sfxHandle_t weaponChangeSound;
+
+
+/*
+===============
+UI_PlayerInfo_SetWeapon
+===============
+*/
+static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum )
+{
+ //TA: FIXME: this is probably useless for trem
+/* gitem_t * item;
+ char path[MAX_QPATH];
+
+ pi->currentWeapon = weaponNum;
+tryagain:
+ pi->realWeapon = weaponNum;
+ pi->weaponModel = 0;
+ pi->barrelModel = 0;
+ pi->flashModel = 0;
+
+ if ( weaponNum == WP_NONE ) {
+ return;
+ }
+
+ if ( item->classname ) {
+ pi->weaponModel = trap_R_RegisterModel( item->world_model[0] );
+ }
+
+ if( pi->weaponModel == 0 ) {
+ if( weaponNum == WP_MACHINEGUN ) {
+ weaponNum = WP_NONE;
+ goto tryagain;
+ }
+ weaponNum = WP_MACHINEGUN;
+ goto tryagain;
+ }
+
+ if ( weaponNum == WP_MACHINEGUN ) {
+ strcpy( path, item->world_model[0] );
+ COM_StripExtension( path, path );
+ strcat( path, "_barrel.md3" );
+ pi->barrelModel = trap_R_RegisterModel( path );
+ }
+
+ strcpy( path, item->world_model[0] );
+ COM_StripExtension( path, path );
+ strcat( path, "_flash.md3" );
+ pi->flashModel = trap_R_RegisterModel( path );
+
+ switch( weaponNum ) {
+ case WP_GAUNTLET:
+ MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
+ break;
+
+ case WP_MACHINEGUN:
+ MAKERGB( pi->flashDlightColor, 1, 1, 0 );
+ break;
+
+ case WP_SHOTGUN:
+ MAKERGB( pi->flashDlightColor, 1, 1, 0 );
+ break;
+
+ case WP_GRENADE_LAUNCHER:
+ MAKERGB( pi->flashDlightColor, 1, 0.7f, 0.5f );
+ break;
+
+ case WP_ROCKET_LAUNCHER:
+ MAKERGB( pi->flashDlightColor, 1, 0.75f, 0 );
+ break;
+
+ case WP_TESLAGEN:
+ MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
+ break;
+
+ case WP_RAILGUN:
+ MAKERGB( pi->flashDlightColor, 1, 0.5f, 0 );
+ break;
+
+ case WP_BFG:
+ MAKERGB( pi->flashDlightColor, 1, 0.7f, 1 );
+ break;
+
+ case WP_GRAPPLING_HOOK:
+ MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
+ break;
+
+ default:
+ MAKERGB( pi->flashDlightColor, 1, 1, 1 );
+ break;
+ }*/
+}
+
+
+/*
+===============
+UI_ForceLegsAnim
+===============
+*/
+static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) {
+ pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
+
+ if ( anim == LEGS_JUMP ) {
+ pi->legsAnimationTimer = UI_TIMER_JUMP;
+ }
+}
+
+
+/*
+===============
+UI_SetLegsAnim
+===============
+*/
+static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) {
+ if ( pi->pendingLegsAnim ) {
+ anim = pi->pendingLegsAnim;
+ pi->pendingLegsAnim = 0;
+ }
+ UI_ForceLegsAnim( pi, anim );
+}
+
+
+/*
+===============
+UI_ForceTorsoAnim
+===============
+*/
+static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) {
+ pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
+
+ if ( anim == TORSO_GESTURE ) {
+ pi->torsoAnimationTimer = UI_TIMER_GESTURE;
+ }
+
+ if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) {
+ pi->torsoAnimationTimer = UI_TIMER_ATTACK;
+ }
+}
+
+
+/*
+===============
+UI_SetTorsoAnim
+===============
+*/
+static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) {
+ if ( pi->pendingTorsoAnim ) {
+ anim = pi->pendingTorsoAnim;
+ pi->pendingTorsoAnim = 0;
+ }
+
+ UI_ForceTorsoAnim( pi, anim );
+}
+
+
+/*
+===============
+UI_TorsoSequencing
+===============
+*/
+static void UI_TorsoSequencing( playerInfo_t *pi ) {
+ int currentAnim;
+
+ currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
+
+ if ( pi->weapon != pi->currentWeapon ) {
+ if ( currentAnim != TORSO_DROP ) {
+ pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
+ UI_ForceTorsoAnim( pi, TORSO_DROP );
+ }
+ }
+
+ if ( pi->torsoAnimationTimer > 0 ) {
+ return;
+ }
+
+ if( currentAnim == TORSO_GESTURE ) {
+ UI_SetTorsoAnim( pi, TORSO_STAND );
+ return;
+ }
+
+ if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) {
+ UI_SetTorsoAnim( pi, TORSO_STAND );
+ return;
+ }
+
+ if ( currentAnim == TORSO_DROP ) {
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+ pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
+ UI_ForceTorsoAnim( pi, TORSO_RAISE );
+ return;
+ }
+
+ if ( currentAnim == TORSO_RAISE ) {
+ UI_SetTorsoAnim( pi, TORSO_STAND );
+ return;
+ }
+}
+
+
+/*
+===============
+UI_LegsSequencing
+===============
+*/
+static void UI_LegsSequencing( playerInfo_t *pi ) {
+ int currentAnim;
+
+ currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
+
+ if ( pi->legsAnimationTimer > 0 ) {
+ if ( currentAnim == LEGS_JUMP ) {
+ jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP );
+ }
+ return;
+ }
+
+ if ( currentAnim == LEGS_JUMP ) {
+ UI_ForceLegsAnim( pi, LEGS_LAND );
+ pi->legsAnimationTimer = UI_TIMER_LAND;
+ jumpHeight = 0;
+ return;
+ }
+
+ if ( currentAnim == LEGS_LAND ) {
+ UI_SetLegsAnim( pi, LEGS_IDLE );
+ return;
+ }
+}
+
+
+/*
+======================
+UI_PositionEntityOnTag
+======================
+*/
+static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ clipHandle_t parentModel, char *tagName ) {
+ int i;
+ orientation_t lerped;
+
+ // lerp the tag
+ trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
+ }
+
+ // cast away const because of compiler problems
+ MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis );
+ entity->backlerp = parent->backlerp;
+}
+
+
+/*
+======================
+UI_PositionRotatedEntityOnTag
+======================
+*/
+static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ clipHandle_t parentModel, char *tagName ) {
+ int i;
+ orientation_t lerped;
+ vec3_t tempAxis[3];
+
+ // lerp the tag
+ trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
+ }
+
+ // cast away const because of compiler problems
+ MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis );
+ MatrixMultiply( lerped.axis, tempAxis, entity->axis );
+}
+
+
+/*
+===============
+UI_SetLerpFrameAnimation
+===============
+*/
+static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
+ animation_t *anim;
+
+ lf->animationNumber = newAnimation;
+ newAnimation &= ~ANIM_TOGGLEBIT;
+
+ if ( newAnimation < 0 || newAnimation >= MAX_PLAYER_ANIMATIONS ) {
+ trap_Error( va("Bad animation number: %i", newAnimation) );
+ }
+
+ anim = &ci->animations[ newAnimation ];
+
+ lf->animation = anim;
+ lf->animationTime = lf->frameTime + anim->initialLerp;
+}
+
+
+/*
+===============
+UI_RunLerpFrame
+===============
+*/
+static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
+ int f;
+ animation_t *anim;
+
+ // see if the animation sequence is switching
+ if ( newAnimation != lf->animationNumber || !lf->animation ) {
+ UI_SetLerpFrameAnimation( ci, lf, newAnimation );
+ }
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if ( dp_realtime >= lf->frameTime ) {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if ( dp_realtime < lf->animationTime ) {
+ lf->frameTime = lf->animationTime; // initial lerp
+ } else {
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+ }
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ if ( f >= anim->numFrames ) {
+ f -= anim->numFrames;
+ if ( anim->loopFrames ) {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ } else {
+ f = anim->numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = dp_realtime;
+ }
+ }
+ lf->frame = anim->firstFrame + f;
+ if ( dp_realtime > lf->frameTime ) {
+ lf->frameTime = dp_realtime;
+ }
+ }
+
+ if ( lf->frameTime > dp_realtime + 200 ) {
+ lf->frameTime = dp_realtime;
+ }
+
+ if ( lf->oldFrameTime > dp_realtime ) {
+ lf->oldFrameTime = dp_realtime;
+ }
+ // calculate current lerp value
+ if ( lf->frameTime == lf->oldFrameTime ) {
+ lf->backlerp = 0;
+ } else {
+ lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+ }
+}
+
+
+/*
+===============
+UI_PlayerAnimation
+===============
+*/
+static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp,
+ int *torsoOld, int *torso, float *torsoBackLerp ) {
+
+ // legs animation
+ pi->legsAnimationTimer -= uiInfo.uiDC.frameTime;
+ if ( pi->legsAnimationTimer < 0 ) {
+ pi->legsAnimationTimer = 0;
+ }
+
+ UI_LegsSequencing( pi );
+
+ if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) {
+ UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN );
+ } else {
+ UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim );
+ }
+ *legsOld = pi->legs.oldFrame;
+ *legs = pi->legs.frame;
+ *legsBackLerp = pi->legs.backlerp;
+
+ // torso animation
+ pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime;
+ if ( pi->torsoAnimationTimer < 0 ) {
+ pi->torsoAnimationTimer = 0;
+ }
+
+ UI_TorsoSequencing( pi );
+
+ UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim );
+ *torsoOld = pi->torso.oldFrame;
+ *torso = pi->torso.frame;
+ *torsoBackLerp = pi->torso.backlerp;
+}
+
+
+/*
+==================
+UI_SwingAngles
+==================
+*/
+static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance,
+ float speed, float *angle, qboolean *swinging ) {
+ float swing;
+ float move;
+ float scale;
+
+ if ( !*swinging ) {
+ // see if a swing should be started
+ swing = AngleSubtract( *angle, destination );
+ if ( swing > swingTolerance || swing < -swingTolerance ) {
+ *swinging = qtrue;
+ }
+ }
+
+ if ( !*swinging ) {
+ return;
+ }
+
+ // modify the speed depending on the delta
+ // so it doesn't seem so linear
+ swing = AngleSubtract( destination, *angle );
+ scale = fabs( swing );
+ if ( scale < swingTolerance * 0.5 ) {
+ scale = 0.5;
+ } else if ( scale < swingTolerance ) {
+ scale = 1.0;
+ } else {
+ scale = 2.0;
+ }
+
+ // swing towards the destination angle
+ if ( swing >= 0 ) {
+ move = uiInfo.uiDC.frameTime * scale * speed;
+ if ( move >= swing ) {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ } else if ( swing < 0 ) {
+ move = uiInfo.uiDC.frameTime * scale * -speed;
+ if ( move <= swing ) {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ }
+
+ // clamp to no more than tolerance
+ swing = AngleSubtract( destination, *angle );
+ if ( swing > clampTolerance ) {
+ *angle = AngleMod( destination - (clampTolerance - 1) );
+ } else if ( swing < -clampTolerance ) {
+ *angle = AngleMod( destination + (clampTolerance - 1) );
+ }
+}
+
+
+/*
+======================
+UI_MovedirAdjustment
+======================
+*/
+static float UI_MovedirAdjustment( playerInfo_t *pi ) {
+ vec3_t relativeAngles;
+ vec3_t moveVector;
+
+ VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles );
+ AngleVectors( relativeAngles, moveVector, NULL, NULL );
+ if ( Q_fabs( moveVector[0] ) < 0.01 ) {
+ moveVector[0] = 0.0;
+ }
+ if ( Q_fabs( moveVector[1] ) < 0.01 ) {
+ moveVector[1] = 0.0;
+ }
+
+ if ( moveVector[1] == 0 && moveVector[0] > 0 ) {
+ return 0;
+ }
+ if ( moveVector[1] < 0 && moveVector[0] > 0 ) {
+ return 22;
+ }
+ if ( moveVector[1] < 0 && moveVector[0] == 0 ) {
+ return 45;
+ }
+ if ( moveVector[1] < 0 && moveVector[0] < 0 ) {
+ return -22;
+ }
+ if ( moveVector[1] == 0 && moveVector[0] < 0 ) {
+ return 0;
+ }
+ if ( moveVector[1] > 0 && moveVector[0] < 0 ) {
+ return 22;
+ }
+ if ( moveVector[1] > 0 && moveVector[0] == 0 ) {
+ return -45;
+ }
+
+ return -22;
+}
+
+
+/*
+===============
+UI_PlayerAngles
+===============
+*/
+static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
+ vec3_t legsAngles, torsoAngles, headAngles;
+ float dest;
+ float adjust;
+
+ VectorCopy( pi->viewAngles, headAngles );
+ headAngles[YAW] = AngleMod( headAngles[YAW] );
+ VectorClear( legsAngles );
+ VectorClear( torsoAngles );
+
+ // --------- yaw -------------
+
+ // allow yaw to drift a bit
+ if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE
+ || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) {
+ // if not standing still, always point all in the same direction
+ pi->torso.yawing = qtrue; // always center
+ pi->torso.pitching = qtrue; // always center
+ pi->legs.yawing = qtrue; // always center
+ }
+
+ // adjust legs for movement dir
+ adjust = UI_MovedirAdjustment( pi );
+ legsAngles[YAW] = headAngles[YAW] + adjust;
+ torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust;
+
+
+ // torso
+ UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing );
+ UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing );
+
+ torsoAngles[YAW] = pi->torso.yawAngle;
+ legsAngles[YAW] = pi->legs.yawAngle;
+
+ // --------- pitch -------------
+
+ // only show a fraction of the pitch angle in the torso
+ if ( headAngles[PITCH] > 180 ) {
+ dest = (-360 + headAngles[PITCH]) * 0.75;
+ } else {
+ dest = headAngles[PITCH] * 0.75;
+ }
+ UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching );
+ torsoAngles[PITCH] = pi->torso.pitchAngle;
+
+ // pull the angles back out of the hierarchial chain
+ AnglesSubtract( headAngles, torsoAngles, headAngles );
+ AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
+ AnglesToAxis( legsAngles, legs );
+ AnglesToAxis( torsoAngles, torso );
+ AnglesToAxis( headAngles, head );
+}
+
+
+/*
+===============
+UI_PlayerFloatSprite
+===============
+*/
+static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) {
+ refEntity_t ent;
+
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( origin, ent.origin );
+ ent.origin[2] += 48;
+ ent.reType = RT_SPRITE;
+ ent.customShader = shader;
+ ent.radius = 10;
+ ent.renderfx = 0;
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+/*
+======================
+UI_MachinegunSpinAngle
+======================
+*/
+float UI_MachinegunSpinAngle( playerInfo_t *pi ) {
+ int delta;
+ float angle;
+ float speed;
+ int torsoAnim;
+
+ delta = dp_realtime - pi->barrelTime;
+ if ( pi->barrelSpinning ) {
+ angle = pi->barrelAngle + delta * SPIN_SPEED;
+ } else {
+ if ( delta > COAST_TIME ) {
+ delta = COAST_TIME;
+ }
+
+ speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
+ angle = pi->barrelAngle + delta * speed;
+ }
+
+ torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
+ if( torsoAnim == TORSO_ATTACK2 ) {
+ torsoAnim = TORSO_ATTACK;
+ }
+ if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) {
+ pi->barrelTime = dp_realtime;
+ pi->barrelAngle = AngleMod( angle );
+ pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK);
+ }
+
+ return angle;
+}
+
+
+/*
+===============
+UI_DrawPlayer
+===============
+*/
+void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) {
+ refdef_t refdef;
+ refEntity_t legs;
+ refEntity_t torso;
+ refEntity_t head;
+ refEntity_t gun;
+ refEntity_t barrel;
+ refEntity_t flash;
+ vec3_t origin;
+ int renderfx;
+ vec3_t mins = {-16, -16, -24};
+ vec3_t maxs = {16, 16, 32};
+ float len;
+ float xx;
+
+ if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) {
+ return;
+ }
+
+ // this allows the ui to cache the player model on the main menu
+ if (w == 0 || h == 0) {
+ return;
+ }
+
+ dp_realtime = time;
+
+ if ( pi->pendingWeapon != WP_NUM_WEAPONS && dp_realtime > pi->weaponTimer ) {
+ pi->weapon = pi->pendingWeapon;
+ pi->lastWeapon = pi->pendingWeapon;
+ pi->pendingWeapon = WP_NUM_WEAPONS;
+ pi->weaponTimer = 0;
+ if( pi->currentWeapon != pi->weapon ) {
+ trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL );
+ }
+ }
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+
+ y -= jumpHeight;
+
+ memset( &refdef, 0, sizeof( refdef ) );
+ memset( &legs, 0, sizeof(legs) );
+ memset( &torso, 0, sizeof(torso) );
+ memset( &head, 0, sizeof(head) );
+
+ refdef.rdflags = RDF_NOWORLDMODEL;
+
+ AxisClear( refdef.viewaxis );
+
+ refdef.x = x;
+ refdef.y = y;
+ refdef.width = w;
+ refdef.height = h;
+
+ refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
+ xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
+ refdef.fov_y = atan2( refdef.height, xx );
+ refdef.fov_y *= ( 360 / (float)M_PI );
+
+ // calculate distance so the player nearly fills the box
+ len = 0.7 * ( maxs[2] - mins[2] );
+ origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 );
+ origin[1] = 0.5 * ( mins[1] + maxs[1] );
+ origin[2] = -0.5 * ( mins[2] + maxs[2] );
+
+ refdef.time = dp_realtime;
+
+ trap_R_ClearScene();
+
+ // get the rotation information
+ UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis );
+
+ // get the animation state (after rotation, to allow feet shuffle)
+ UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp,
+ &torso.oldframe, &torso.frame, &torso.backlerp );
+
+ renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
+
+ //
+ // add the legs
+ //
+ legs.hModel = pi->legsModel;
+ legs.customSkin = pi->legsSkin;
+
+ VectorCopy( origin, legs.origin );
+
+ VectorCopy( origin, legs.lightingOrigin );
+ legs.renderfx = renderfx;
+ VectorCopy (legs.origin, legs.oldorigin);
+
+ trap_R_AddRefEntityToScene( &legs );
+
+ if (!legs.hModel) {
+ return;
+ }
+
+ //
+ // add the torso
+ //
+ torso.hModel = pi->torsoModel;
+ if (!torso.hModel) {
+ return;
+ }
+
+ torso.customSkin = pi->torsoSkin;
+
+ VectorCopy( origin, torso.lightingOrigin );
+
+ UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso");
+
+ torso.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &torso );
+
+ //
+ // add the head
+ //
+ head.hModel = pi->headModel;
+ if (!head.hModel) {
+ return;
+ }
+ head.customSkin = pi->headSkin;
+
+ VectorCopy( origin, head.lightingOrigin );
+
+ UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head");
+
+ head.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &head );
+
+ //
+ // add the gun
+ //
+ if ( pi->currentWeapon != WP_NONE ) {
+ memset( &gun, 0, sizeof(gun) );
+ gun.hModel = pi->weaponModel;
+ VectorCopy( origin, gun.lightingOrigin );
+ UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon");
+ gun.renderfx = renderfx;
+ trap_R_AddRefEntityToScene( &gun );
+ }
+
+ //
+ // add the spinning barrel
+ //
+ if ( pi->realWeapon == WP_MACHINEGUN ) {
+ vec3_t angles;
+
+ memset( &barrel, 0, sizeof(barrel) );
+ VectorCopy( origin, barrel.lightingOrigin );
+ barrel.renderfx = renderfx;
+
+ barrel.hModel = pi->barrelModel;
+ angles[YAW] = 0;
+ angles[PITCH] = 0;
+ angles[ROLL] = UI_MachinegunSpinAngle( pi );
+/* if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
+ angles[PITCH] = angles[ROLL];
+ angles[ROLL] = 0;
+ }*/
+ AnglesToAxis( angles, barrel.axis );
+
+ UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel");
+
+ trap_R_AddRefEntityToScene( &barrel );
+ }
+
+ //
+ // add muzzle flash
+ //
+ if ( dp_realtime <= pi->muzzleFlashTime ) {
+ if ( pi->flashModel ) {
+ memset( &flash, 0, sizeof(flash) );
+ flash.hModel = pi->flashModel;
+ VectorCopy( origin, flash.lightingOrigin );
+ UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash");
+ flash.renderfx = renderfx;
+ trap_R_AddRefEntityToScene( &flash );
+ }
+
+ // make a dlight for the flash
+ if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) {
+ trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0],
+ pi->flashDlightColor[1], pi->flashDlightColor[2] );
+ }
+ }
+
+ //
+ // add the chat icon
+ //
+ if ( pi->chat ) {
+ UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) );
+ }
+
+ //
+ // add an accent light
+ //
+ origin[0] -= 100; // + = behind, - = in front
+ origin[1] += 100; // + = left, - = right
+ origin[2] += 100; // + = above, - = below
+ trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 );
+
+ origin[0] -= 100;
+ origin[1] -= 100;
+ origin[2] -= 100;
+ trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 );
+
+ trap_R_RenderScene( &refdef );
+}
+
+/*
+==========================
+UI_FileExists
+==========================
+*/
+static qboolean UI_FileExists(const char *filename) {
+ int len;
+
+ len = trap_FS_FOpenFile( filename, NULL, FS_READ );
+ if (len>0) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==========================
+UI_FindClientHeadFile
+==========================
+*/
+static qboolean UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) {
+ char *team, *headsFolder;
+ int i;
+
+ team = "default";
+
+ if ( headModelName[0] == '*' ) {
+ headsFolder = "heads/";
+ headModelName++;
+ }
+ else {
+ headsFolder = "";
+ }
+ while(1) {
+ for ( i = 0; i < 2; i++ ) {
+ if ( i == 0 && teamName && *teamName ) {
+ Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext );
+ }
+ else {
+ Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext );
+ }
+ if ( UI_FileExists( filename ) ) {
+ return qtrue;
+ }
+ if ( i == 0 && teamName && *teamName ) {
+ Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext );
+ }
+ else {
+ Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext );
+ }
+ if ( UI_FileExists( filename ) ) {
+ return qtrue;
+ }
+ if ( !teamName || !*teamName ) {
+ break;
+ }
+ }
+ // if tried the heads folder first
+ if ( headsFolder[0] ) {
+ break;
+ }
+ headsFolder = "heads/";
+ }
+
+ return qfalse;
+}
+
+/*
+==========================
+UI_RegisterClientSkin
+==========================
+*/
+static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) {
+ char filename[MAX_QPATH*2];
+
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName );
+ }
+ pi->legsSkin = trap_R_RegisterSkin( filename );
+ if (!pi->legsSkin) {
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower_%s.skin", modelName, skinName );
+ }
+ pi->legsSkin = trap_R_RegisterSkin( filename );
+ }
+
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName );
+ }
+ pi->torsoSkin = trap_R_RegisterSkin( filename );
+ if (!pi->torsoSkin) {
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper_%s.skin", modelName, skinName );
+ }
+ pi->torsoSkin = trap_R_RegisterSkin( filename );
+ }
+
+ if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) {
+ pi->headSkin = trap_R_RegisterSkin( filename );
+ }
+
+ if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+/*
+======================
+UI_ParseAnimationFile
+======================
+*/
+static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) {
+ char *text_p, *prev;
+ int len;
+ int i;
+ char *token;
+ float fps;
+ int skip;
+ char text[20000];
+ fileHandle_t f;
+
+ memset( animations, 0, sizeof( animation_t ) * MAX_PLAYER_ANIMATIONS );
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( len <= 0 ) {
+ return qfalse;
+ }
+ if ( len >= ( sizeof( text ) - 1 ) ) {
+ Com_Printf( "File %s too long\n", filename );
+ trap_FS_FCloseFile( f );
+ return qfalse;
+ }
+ trap_FS_Read( text, len, f );
+ text[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ COM_Compress(text);
+
+ // parse the text
+ text_p = text;
+ skip = 0; // quite the compiler warning
+
+ // read optional parameters
+ while ( 1 ) {
+ prev = text_p; // so we can unget
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ if ( !Q_stricmp( token, "footsteps" ) ) {
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ continue;
+ } else if ( !Q_stricmp( token, "headoffset" ) ) {
+ for ( i = 0 ; i < 3 ; i++ ) {
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ }
+ continue;
+ } else if ( !Q_stricmp( token, "sex" ) ) {
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ continue;
+ }
+
+ // if it is a number, start parsing animations
+ if ( token[0] >= '0' && token[0] <= '9' ) {
+ text_p = prev; // unget the token
+ break;
+ }
+
+ Com_Printf( "unknown token '%s' is %s\n", token, filename );
+ }
+
+ // read information for each frame
+ for ( i = 0 ; i < MAX_PLAYER_ANIMATIONS ; i++ ) {
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ animations[i].firstFrame = atoi( token );
+ // leg only frames are adjusted to not count the upper body only frames
+ if ( i == LEGS_WALKCR ) {
+ skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
+ }
+ if ( i >= LEGS_WALKCR ) {
+ animations[i].firstFrame -= skip;
+ }
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ animations[i].numFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ animations[i].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ fps = atof( token );
+ if ( fps == 0 ) {
+ fps = 1;
+ }
+ animations[i].frameLerp = 1000 / fps;
+ animations[i].initialLerp = 1000 / fps;
+ }
+
+ if ( i != MAX_PLAYER_ANIMATIONS ) {
+ Com_Printf( "Error parsing animation file: %s", filename );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+==========================
+UI_RegisterClientModelname
+==========================
+*/
+qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) {
+ char modelName[MAX_QPATH];
+ char skinName[MAX_QPATH];
+ char headModelName[MAX_QPATH];
+ char headSkinName[MAX_QPATH];
+ char filename[MAX_QPATH];
+ char *slash;
+
+ pi->torsoModel = 0;
+ pi->headModel = 0;
+
+ if ( !modelSkinName[0] ) {
+ return qfalse;
+ }
+
+ Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) );
+
+ slash = strchr( modelName, '/' );
+ if ( !slash ) {
+ // modelName did not include a skin name
+ Q_strncpyz( skinName, "default", sizeof( skinName ) );
+ } else {
+ Q_strncpyz( skinName, slash + 1, sizeof( skinName ) );
+ *slash = '\0';
+ }
+
+ Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) );
+ slash = strchr( headModelName, '/' );
+ if ( !slash ) {
+ // modelName did not include a skin name
+ Q_strncpyz( headSkinName, "default", sizeof( skinName ) );
+ } else {
+ Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) );
+ *slash = '\0';
+ }
+
+ // load cmodels before models so filecache works
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName );
+ pi->legsModel = trap_R_RegisterModel( filename );
+ if ( !pi->legsModel ) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName );
+ pi->legsModel = trap_R_RegisterModel( filename );
+ if ( !pi->legsModel ) {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName );
+ pi->torsoModel = trap_R_RegisterModel( filename );
+ if ( !pi->torsoModel ) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName );
+ pi->torsoModel = trap_R_RegisterModel( filename );
+ if ( !pi->torsoModel ) {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ if (headModelName[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", headModelName );
+ }
+ pi->headModel = trap_R_RegisterModel( filename );
+ if ( !pi->headModel && headModelName[0] != '*') {
+ Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName );
+ pi->headModel = trap_R_RegisterModel( filename );
+ }
+
+ if (!pi->headModel) {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+
+ // if any skins failed to load, fall back to default
+ if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) {
+ if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) {
+ Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
+ return qfalse;
+ }
+ }
+
+ // load the animations
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
+ if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName );
+ if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
+ Com_Printf( "Failed to load animation file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ return qtrue;
+}
+
+
+/*
+===============
+UI_PlayerInfo_SetModel
+===============
+*/
+void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) {
+ memset( pi, 0, sizeof(*pi) );
+ UI_RegisterClientModelname( pi, model, headmodel, teamName );
+ pi->weapon = WP_MACHINEGUN;
+ pi->currentWeapon = pi->weapon;
+ pi->lastWeapon = pi->weapon;
+ pi->pendingWeapon = -1;
+ pi->weaponTimer = 0;
+ pi->chat = qfalse;
+ pi->newModel = qtrue;
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+}
+
+
+/*
+===============
+UI_PlayerInfo_SetInfo
+===============
+*/
+void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) {
+ int currentAnim;
+ weapon_t weaponNum;
+
+ pi->chat = chat;
+
+ // view angles
+ VectorCopy( viewAngles, pi->viewAngles );
+
+ // move angles
+ VectorCopy( moveAngles, pi->moveAngles );
+
+ if ( pi->newModel ) {
+ pi->newModel = qfalse;
+
+ jumpHeight = 0;
+ pi->pendingLegsAnim = 0;
+ UI_ForceLegsAnim( pi, legsAnim );
+ pi->legs.yawAngle = viewAngles[YAW];
+ pi->legs.yawing = qfalse;
+
+ pi->pendingTorsoAnim = 0;
+ UI_ForceTorsoAnim( pi, torsoAnim );
+ pi->torso.yawAngle = viewAngles[YAW];
+ pi->torso.yawing = qfalse;
+
+ if ( weaponNumber != WP_NUM_WEAPONS ) {
+ pi->weapon = weaponNumber;
+ pi->currentWeapon = weaponNumber;
+ pi->lastWeapon = weaponNumber;
+ pi->pendingWeapon = WP_NUM_WEAPONS;
+ pi->weaponTimer = 0;
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+ }
+
+ return;
+ }
+
+ // weapon
+ if ( weaponNumber == WP_NUM_WEAPONS ) {
+ pi->pendingWeapon = WP_NUM_WEAPONS;
+ pi->weaponTimer = 0;
+ }
+ else if ( weaponNumber != WP_NONE ) {
+ pi->pendingWeapon = weaponNumber;
+ pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY;
+ }
+ weaponNum = pi->lastWeapon;
+ pi->weapon = weaponNum;
+
+ if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) {
+ torsoAnim = legsAnim = BOTH_DEATH1;
+ pi->weapon = pi->currentWeapon = WP_NONE;
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+
+ jumpHeight = 0;
+ pi->pendingLegsAnim = 0;
+ UI_ForceLegsAnim( pi, legsAnim );
+
+ pi->pendingTorsoAnim = 0;
+ UI_ForceTorsoAnim( pi, torsoAnim );
+
+ return;
+ }
+
+ // leg animation
+ currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
+ if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) {
+ pi->pendingLegsAnim = legsAnim;
+ }
+ else if ( legsAnim != currentAnim ) {
+ jumpHeight = 0;
+ pi->pendingLegsAnim = 0;
+ UI_ForceLegsAnim( pi, legsAnim );
+ }
+
+ // torso animation
+ if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) {
+ if ( weaponNum == WP_NONE ) {
+ torsoAnim = TORSO_STAND2;
+ }
+ else {
+ torsoAnim = TORSO_STAND;
+ }
+ }
+
+ if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) {
+ if ( weaponNum == WP_NONE ) {
+ torsoAnim = TORSO_ATTACK2;
+ }
+ else {
+ torsoAnim = TORSO_ATTACK;
+ }
+ pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
+ //FIXME play firing sound here
+ }
+
+ currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
+
+ if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) {
+ pi->pendingTorsoAnim = torsoAnim;
+ }
+ else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) {
+ pi->pendingTorsoAnim = torsoAnim;
+ }
+ else if ( torsoAnim != currentAnim ) {
+ pi->pendingTorsoAnim = 0;
+ UI_ForceTorsoAnim( pi, torsoAnim );
+ }
+}
diff --git a/src/ui/ui_public.h b/src/ui/ui_public.h
new file mode 100644
index 0000000..f62b8b9
--- /dev/null
+++ b/src/ui/ui_public.h
@@ -0,0 +1,210 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef __UI_PUBLIC_H__
+#define __UI_PUBLIC_H__
+
+#define UI_API_VERSION 6
+
+typedef struct {
+ connstate_t connState;
+ int connectPacketCount;
+ int clientNum;
+ char servername[MAX_STRING_CHARS];
+ char updateInfoString[MAX_STRING_CHARS];
+ char messageString[MAX_STRING_CHARS];
+} uiClientState_t;
+
+typedef enum {
+ UI_ERROR,
+ UI_PRINT,
+ UI_MILLISECONDS,
+ UI_CVAR_SET,
+ UI_CVAR_VARIABLEVALUE,
+ UI_CVAR_VARIABLESTRINGBUFFER,
+ UI_CVAR_SETVALUE,
+ UI_CVAR_RESET,
+ UI_CVAR_CREATE,
+ UI_CVAR_INFOSTRINGBUFFER,
+ UI_ARGC,
+ UI_ARGV,
+ UI_CMD_EXECUTETEXT,
+ UI_FS_FOPENFILE,
+ UI_FS_READ,
+ UI_FS_WRITE,
+ UI_FS_FCLOSEFILE,
+ UI_FS_GETFILELIST,
+ UI_R_REGISTERMODEL,
+ UI_R_REGISTERSKIN,
+ UI_R_REGISTERSHADERNOMIP,
+ UI_R_CLEARSCENE,
+ UI_R_ADDREFENTITYTOSCENE,
+ UI_R_ADDPOLYTOSCENE,
+ UI_R_ADDLIGHTTOSCENE,
+ UI_R_RENDERSCENE,
+ UI_R_SETCOLOR,
+#ifndef MODULE_INTERFACE_11
+ UI_R_SETCLIPREGION,
+#endif
+ UI_R_DRAWSTRETCHPIC,
+ UI_UPDATESCREEN,
+ UI_CM_LERPTAG,
+ UI_CM_LOADMODEL,
+ UI_S_REGISTERSOUND,
+ UI_S_STARTLOCALSOUND,
+ UI_KEY_KEYNUMTOSTRINGBUF,
+ UI_KEY_GETBINDINGBUF,
+ UI_KEY_SETBINDING,
+ UI_KEY_ISDOWN,
+ UI_KEY_GETOVERSTRIKEMODE,
+ UI_KEY_SETOVERSTRIKEMODE,
+ UI_KEY_CLEARSTATES,
+ UI_KEY_GETCATCHER,
+ UI_KEY_SETCATCHER,
+ UI_GETCLIPBOARDDATA,
+ UI_GETGLCONFIG,
+ UI_GETCLIENTSTATE,
+ UI_GETCONFIGSTRING,
+ UI_LAN_GETPINGQUEUECOUNT,
+ UI_LAN_CLEARPING,
+ UI_LAN_GETPING,
+ UI_LAN_GETPINGINFO,
+ UI_CVAR_REGISTER,
+ UI_CVAR_UPDATE,
+ UI_MEMORY_REMAINING,
+ UI_R_REGISTERFONT,
+ UI_R_MODELBOUNDS,
+#ifdef MODULE_INTERFACE_11
+ UI_PARSE_ADD_GLOBAL_DEFINE,
+ UI_PARSE_LOAD_SOURCE,
+ UI_PARSE_FREE_SOURCE,
+ UI_PARSE_READ_TOKEN,
+ UI_PARSE_SOURCE_FILE_AND_LINE,
+#endif
+ UI_S_STOPBACKGROUNDTRACK,
+ UI_S_STARTBACKGROUNDTRACK,
+ UI_REAL_TIME,
+ UI_LAN_GETSERVERCOUNT,
+ UI_LAN_GETSERVERADDRESSSTRING,
+ UI_LAN_GETSERVERINFO,
+ UI_LAN_MARKSERVERVISIBLE,
+ UI_LAN_UPDATEVISIBLEPINGS,
+ UI_LAN_RESETPINGS,
+ UI_LAN_LOADCACHEDSERVERS,
+ UI_LAN_SAVECACHEDSERVERS,
+ UI_LAN_ADDSERVER,
+ UI_LAN_REMOVESERVER,
+ UI_CIN_PLAYCINEMATIC,
+ UI_CIN_STOPCINEMATIC,
+ UI_CIN_RUNCINEMATIC,
+ UI_CIN_DRAWCINEMATIC,
+ UI_CIN_SETEXTENTS,
+ UI_R_REMAP_SHADER,
+ UI_LAN_SERVERSTATUS,
+ UI_LAN_GETSERVERPING,
+ UI_LAN_SERVERISVISIBLE,
+ UI_LAN_COMPARESERVERS,
+ // 1.32
+ UI_FS_SEEK,
+ UI_SET_PBCLSTATUS,
+
+#ifndef MODULE_INTERFACE_11
+ UI_PARSE_ADD_GLOBAL_DEFINE,
+ UI_PARSE_LOAD_SOURCE,
+ UI_PARSE_FREE_SOURCE,
+ UI_PARSE_READ_TOKEN,
+ UI_PARSE_SOURCE_FILE_AND_LINE,
+ UI_GETNEWS,
+#endif
+
+ UI_MEMSET = 100,
+ UI_MEMCPY,
+ UI_STRNCPY,
+ UI_SIN,
+ UI_COS,
+ UI_ATAN2,
+ UI_SQRT,
+ UI_FLOOR,
+ UI_CEIL
+} uiImport_t;
+
+typedef enum {
+ UIMENU_NONE,
+ UIMENU_MAIN,
+ UIMENU_INGAME,
+ UIMENU_TEAM,
+ UIMENU_POSTGAME
+} uiMenuCommand_t;
+
+typedef enum
+{
+ SORT_HOST,
+ SORT_MAP,
+ SORT_CLIENTS,
+ SORT_PING
+} serverSortField_t;
+
+typedef enum {
+ UI_GETAPIVERSION = 0, // system reserved
+
+ UI_INIT,
+// void UI_Init( void );
+
+ UI_SHUTDOWN,
+// void UI_Shutdown( void );
+
+ UI_KEY_EVENT,
+// void UI_KeyEvent( int key );
+
+ UI_MOUSE_EVENT,
+// void UI_MouseEvent( int dx, int dy );
+
+#ifndef MODULE_INTERFACE_11
+ UI_MOUSE_POSITION,
+// int UI_MousePosition( void );
+
+ UI_SET_MOUSE_POSITION,
+// void UI_SetMousePosition( int x, int y );
+#endif
+
+ UI_REFRESH,
+// void UI_Refresh( int time );
+
+ UI_IS_FULLSCREEN,
+// qboolean UI_IsFullscreen( void );
+
+ UI_SET_ACTIVE_MENU,
+// void UI_SetActiveMenu( uiMenuCommand_t menu );
+
+ UI_CONSOLE_COMMAND,
+// qboolean UI_ConsoleCommand( int realTime );
+
+ UI_DRAW_CONNECT_SCREEN
+// void UI_DrawConnectScreen( qboolean overlay );
+
+// if !overlay, the background will be drawn, otherwise it will be
+// overlayed over whatever the cgame has drawn.
+// a GetClientState syscall will be made to get the current strings
+} uiExport_t;
+
+#endif
diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c
new file mode 100644
index 0000000..5640e0e
--- /dev/null
+++ b/src/ui/ui_shared.c
@@ -0,0 +1,6115 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "ui_shared.h"
+
+#define SCROLL_TIME_START 500
+#define SCROLL_TIME_ADJUST 150
+#define SCROLL_TIME_ADJUSTOFFSET 40
+#define SCROLL_TIME_FLOOR 20
+
+typedef struct scrollInfo_s {
+ int nextScrollTime;
+ int nextAdjustTime;
+ int adjustValue;
+ int scrollKey;
+ float xStart;
+ float yStart;
+ itemDef_t *item;
+ qboolean scrollDir;
+} scrollInfo_t;
+
+static scrollInfo_t scrollInfo;
+
+//TA: hack to prevent compiler warnings
+void voidFunction( void *var ) { return; }
+qboolean voidFunction2( itemDef_t *var1, int var2 ) { return qfalse; }
+
+static void (*captureFunc) (void *p) = voidFunction;
+static void *captureData = NULL;
+static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any )
+
+displayContextDef_t *DC = NULL;
+
+static qboolean g_waitingForKey = qfalse;
+static qboolean g_editingField = qfalse;
+
+static itemDef_t *g_bindItem = NULL;
+static itemDef_t *g_editItem = NULL;
+
+menuDef_t Menus[MAX_MENUS]; // defined menus
+int menuCount = 0; // how many
+
+menuDef_t *menuStack[MAX_OPEN_MENUS];
+int openMenuCount = 0;
+
+static qboolean debugMode = qfalse;
+
+#define DOUBLE_CLICK_DELAY 300
+static int lastListBoxClickTime = 0;
+
+void Item_RunScript(itemDef_t *item, const char *s);
+void Item_SetupKeywordHash(void);
+void Menu_SetupKeywordHash(void);
+int BindingIDFromName(const char *name);
+qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down);
+itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu);
+itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu);
+static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y);
+
+#ifdef CGAME
+#define MEM_POOL_SIZE 128 * 1024
+#else
+#define MEM_POOL_SIZE 1024 * 1024
+#endif
+
+//TA: hacked variable name to avoid conflict with new cgame Alloc
+static char UI_memoryPool[MEM_POOL_SIZE];
+static int allocPoint, outOfMemory;
+
+/*
+===============
+UI_Alloc
+===============
+*/
+void *UI_Alloc( int size )
+{
+ char *p;
+
+ if( allocPoint + size > MEM_POOL_SIZE )
+ {
+ outOfMemory = qtrue;
+
+ if( DC->Print )
+ DC->Print( "UI_Alloc: Failure. Out of memory!\n" );
+ //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n");
+ return NULL;
+ }
+
+ p = &UI_memoryPool[ allocPoint ];
+
+ allocPoint += ( size + 15 ) & ~15;
+
+ return p;
+}
+
+/*
+===============
+UI_InitMemory
+===============
+*/
+void UI_InitMemory( void )
+{
+ allocPoint = 0;
+ outOfMemory = qfalse;
+}
+
+qboolean UI_OutOfMemory( )
+{
+ return outOfMemory;
+}
+
+
+
+
+
+#define HASH_TABLE_SIZE 2048
+/*
+================
+return a hash value for the string
+================
+*/
+static long hashForString(const char *str) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (str[i] != '\0') {
+ letter = tolower(str[i]);
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash &= (HASH_TABLE_SIZE-1);
+ return hash;
+}
+
+typedef struct stringDef_s {
+ struct stringDef_s *next;
+ const char *str;
+} stringDef_t;
+
+static int strPoolIndex = 0;
+static char strPool[STRING_POOL_SIZE];
+
+static int strHandleCount = 0;
+static stringDef_t *strHandle[HASH_TABLE_SIZE];
+
+
+const char *String_Alloc(const char *p) {
+ int len;
+ long hash;
+ stringDef_t *str, *last;
+ static const char *staticNULL = "";
+
+ if (p == NULL) {
+ return NULL;
+ }
+
+ if (*p == 0) {
+ return staticNULL;
+ }
+
+ hash = hashForString(p);
+
+ str = strHandle[hash];
+ while (str) {
+ if (strcmp(p, str->str) == 0) {
+ return str->str;
+ }
+ str = str->next;
+ }
+
+ len = strlen(p);
+ if (len + strPoolIndex + 1 < STRING_POOL_SIZE) {
+ int ph = strPoolIndex;
+ strcpy(&strPool[strPoolIndex], p);
+ strPoolIndex += len + 1;
+
+ str = strHandle[hash];
+ last = str;
+ while (str && str->next) {
+ last = str;
+ str = str->next;
+ }
+
+ str = UI_Alloc(sizeof(stringDef_t));
+ str->next = NULL;
+ str->str = &strPool[ph];
+ if (last) {
+ last->next = str;
+ } else {
+ strHandle[hash] = str;
+ }
+ return &strPool[ph];
+ }
+ return NULL;
+}
+
+void String_Report( void ) {
+ float f;
+ Com_Printf("Memory/String Pool Info\n");
+ Com_Printf("----------------\n");
+ f = strPoolIndex;
+ f /= STRING_POOL_SIZE;
+ f *= 100;
+ Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE);
+ f = allocPoint;
+ f /= MEM_POOL_SIZE;
+ f *= 100;
+ Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE);
+}
+
+/*
+=================
+String_Init
+=================
+*/
+void String_Init( void )
+{
+ int i;
+ for( i = 0; i < HASH_TABLE_SIZE; i++ )
+ strHandle[ i ] = 0;
+
+ strHandleCount = 0;
+ strPoolIndex = 0;
+ menuCount = 0;
+ openMenuCount = 0;
+ UI_InitMemory( );
+ Item_SetupKeywordHash( );
+ Menu_SetupKeywordHash( );
+
+ if( DC && DC->getBindingBuf )
+ Controls_GetConfig( );
+}
+
+/*
+=================
+PC_SourceWarning
+=================
+*/
+void PC_SourceWarning(int handle, char *format, ...) {
+ int line;
+ char filename[128];
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ filename[0] = '\0';
+ line = 0;
+ trap_Parse_SourceFileAndLine(handle, filename, &line);
+
+ Com_Printf(S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string);
+}
+
+/*
+=================
+PC_SourceError
+=================
+*/
+void PC_SourceError(int handle, char *format, ...) {
+ int line;
+ char filename[128];
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ filename[0] = '\0';
+ line = 0;
+ trap_Parse_SourceFileAndLine(handle, filename, &line);
+
+ Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string);
+}
+
+/*
+=================
+LerpColor
+=================
+*/
+void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t)
+{
+ int i;
+
+ // lerp and clamp each component
+ for (i=0; i<4; i++)
+ {
+ c[i] = a[i] + t*(b[i]-a[i]);
+ if (c[i] < 0)
+ c[i] = 0;
+ else if (c[i] > 1.0)
+ c[i] = 1.0;
+ }
+}
+
+/*
+=================
+Float_Parse
+=================
+*/
+qboolean Float_Parse(char **p, float *f) {
+ char *token;
+ token = COM_ParseExt(p, qfalse);
+ if (token && token[0] != 0) {
+ *f = atof(token);
+ return qtrue;
+ } else {
+ return qfalse;
+ }
+}
+
+/*
+=================
+PC_Float_Parse
+=================
+*/
+qboolean PC_Float_Parse(int handle, float *f) {
+ pc_token_t token;
+ int negative = qfalse;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (token.string[0] == '-') {
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ negative = qtrue;
+ }
+ if (token.type != TT_NUMBER) {
+ PC_SourceError(handle, "expected float but found %s\n", token.string);
+ return qfalse;
+ }
+ if (negative)
+ *f = -token.floatvalue;
+ else
+ *f = token.floatvalue;
+ return qtrue;
+}
+
+/*
+=================
+Color_Parse
+=================
+*/
+qboolean Color_Parse(char **p, vec4_t *c) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!Float_Parse(p, &f)) {
+ return qfalse;
+ }
+ (*c)[i] = f;
+ }
+ return qtrue;
+}
+
+/*
+=================
+PC_Color_Parse
+=================
+*/
+qboolean PC_Color_Parse(int handle, vec4_t *c) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ (*c)[i] = f;
+ }
+ return qtrue;
+}
+
+/*
+=================
+Int_Parse
+=================
+*/
+qboolean Int_Parse(char **p, int *i) {
+ char *token;
+ token = COM_ParseExt(p, qfalse);
+
+ if (token && token[0] != 0) {
+ *i = atoi(token);
+ return qtrue;
+ } else {
+ return qfalse;
+ }
+}
+
+/*
+=================
+PC_Int_Parse
+=================
+*/
+qboolean PC_Int_Parse(int handle, int *i) {
+ pc_token_t token;
+ int negative = qfalse;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (token.string[0] == '-') {
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ negative = qtrue;
+ }
+ if (token.type != TT_NUMBER) {
+ PC_SourceError(handle, "expected integer but found %s\n", token.string);
+ return qfalse;
+ }
+ *i = token.intvalue;
+ if (negative)
+ *i = - *i;
+ return qtrue;
+}
+
+/*
+=================
+Rect_Parse
+=================
+*/
+qboolean Rect_Parse(char **p, rectDef_t *r) {
+ if (Float_Parse(p, &r->x)) {
+ if (Float_Parse(p, &r->y)) {
+ if (Float_Parse(p, &r->w)) {
+ if (Float_Parse(p, &r->h)) {
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+PC_Rect_Parse
+=================
+*/
+qboolean PC_Rect_Parse(int handle, rectDef_t *r) {
+ if (PC_Float_Parse(handle, &r->x)) {
+ if (PC_Float_Parse(handle, &r->y)) {
+ if (PC_Float_Parse(handle, &r->w)) {
+ if (PC_Float_Parse(handle, &r->h)) {
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+String_Parse
+=================
+*/
+qboolean String_Parse(char **p, const char **out) {
+ char *token;
+
+ token = COM_ParseExt(p, qfalse);
+ if (token && token[0] != 0) {
+ *(out) = String_Alloc(token);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+=================
+PC_String_Parse
+=================
+*/
+qboolean PC_String_Parse(int handle, const char **out) {
+ pc_token_t token;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ *(out) = String_Alloc(token.string);
+ return qtrue;
+}
+
+/*
+=================
+PC_Script_Parse
+=================
+*/
+qboolean PC_Script_Parse(int handle, const char **out) {
+ char script[1024];
+ pc_token_t token;
+
+ memset(script, 0, sizeof(script));
+ // scripts start with { and have ; separated command lists.. commands are command, arg..
+ // basically we want everything between the { } as it will be interpreted at run time
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (Q_stricmp(token.string, "{") != 0) {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ if (Q_stricmp(token.string, "}") == 0) {
+ *out = String_Alloc(script);
+ return qtrue;
+ }
+
+ if (token.string[1] != '\0') {
+ Q_strcat(script, 1024, va("\"%s\"", token.string));
+ } else {
+ Q_strcat(script, 1024, token.string);
+ }
+ Q_strcat(script, 1024, " ");
+ }
+ return qfalse; // bk001105 - LCC missing return value
+}
+
+// display, window, menu, item code
+//
+
+/*
+==================
+Init_Display
+
+Initializes the display with a structure to all the drawing routines
+==================
+*/
+void Init_Display( displayContextDef_t *dc )
+{
+ DC = dc;
+}
+
+
+
+// type and style painting
+
+void GradientBar_Paint( rectDef_t *rect, vec4_t color )
+{
+ // gradient bar takes two paints
+ DC->setColor( color );
+ DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar );
+ DC->setColor( NULL );
+}
+
+
+/*
+==================
+Window_Init
+
+Initializes a window structure ( windowDef_t ) with defaults
+
+==================
+*/
+void Window_Init(Window *w) {
+ memset(w, 0, sizeof(windowDef_t));
+ w->borderSize = 1;
+ w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0;
+ w->cinematic = -1;
+}
+
+void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount) {
+ if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN)) {
+ if (DC->realTime > *nextTime) {
+ *nextTime = DC->realTime + offsetTime;
+ if (*flags & WINDOW_FADINGOUT) {
+ *f -= fadeAmount;
+ if (bFlags && *f <= 0.0) {
+ *flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE);
+ }
+ } else {
+ *f += fadeAmount;
+ if (*f >= clamp) {
+ *f = clamp;
+ if (bFlags) {
+ *flags &= ~WINDOW_FADINGIN;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle) {
+ //float bordersize = 0;
+ vec4_t color;
+ rectDef_t fillRect = w->rect;
+
+
+ if (debugMode) {
+ color[0] = color[1] = color[2] = color[3] = 1;
+ DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color);
+ }
+
+ if (w == NULL || (w->style == 0 && w->border == 0)) {
+ return;
+ }
+
+ if (w->border != 0) {
+ fillRect.x += w->borderSize;
+ fillRect.y += w->borderSize;
+ fillRect.w -= w->borderSize + 1;
+ fillRect.h -= w->borderSize + 1;
+ }
+
+ if (w->style == WINDOW_STYLE_FILLED) {
+ // box, but possible a shader that needs filled
+ if (w->background) {
+ Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount);
+ DC->setColor(w->backColor);
+ DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
+ DC->setColor(NULL);
+ } else {
+ DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor);
+ }
+ } else if (w->style == WINDOW_STYLE_GRADIENT) {
+ GradientBar_Paint(&fillRect, w->backColor);
+ // gradient bar
+ } else if (w->style == WINDOW_STYLE_SHADER) {
+ if (w->flags & WINDOW_FORECOLORSET) {
+ DC->setColor(w->foreColor);
+ }
+ DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
+ DC->setColor(NULL);
+ } else if (w->style == WINDOW_STYLE_TEAMCOLOR) {
+ if (DC->getTeamColor) {
+ DC->getTeamColor(&color);
+ DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, color);
+ }
+ } else if (w->style == WINDOW_STYLE_CINEMATIC) {
+ if (w->cinematic == -1) {
+ w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
+ if (w->cinematic == -1) {
+ w->cinematic = -2;
+ }
+ }
+ if (w->cinematic >= 0) {
+ DC->runCinematicFrame(w->cinematic);
+ DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
+ }
+ }
+
+ if (w->border == WINDOW_BORDER_FULL) {
+ // full
+ // HACK HACK HACK
+ if (w->style == WINDOW_STYLE_TEAMCOLOR) {
+ if (color[0] > 0) {
+ // red
+ color[0] = 1;
+ color[1] = color[2] = .5;
+
+ } else {
+ color[2] = 1;
+ color[0] = color[1] = .5;
+ }
+ color[3] = 1;
+ DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, color);
+ } else {
+ DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor);
+ }
+ } else if (w->border == WINDOW_BORDER_HORZ) {
+ // top/bottom
+ DC->setColor(w->borderColor);
+ DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
+ DC->setColor( NULL );
+ } else if (w->border == WINDOW_BORDER_VERT) {
+ // left right
+ DC->setColor(w->borderColor);
+ DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
+ DC->setColor( NULL );
+ } else if (w->border == WINDOW_BORDER_KCGRADIENT) {
+ // this is just two gradient bars along each horz edge
+ rectDef_t r = w->rect;
+ r.h = w->borderSize;
+ GradientBar_Paint(&r, w->borderColor);
+ r.y = w->rect.y + w->rect.h - 1;
+ GradientBar_Paint(&r, w->borderColor);
+ }
+
+}
+
+
+void Item_SetScreenCoords(itemDef_t *item, float x, float y) {
+
+ if (item == NULL) {
+ return;
+ }
+
+ if (item->window.border != 0) {
+ x += item->window.borderSize;
+ y += item->window.borderSize;
+ }
+
+ item->window.rect.x = x + item->window.rectClient.x;
+ item->window.rect.y = y + item->window.rectClient.y;
+ item->window.rect.w = item->window.rectClient.w;
+ item->window.rect.h = item->window.rectClient.h;
+
+ // force the text rects to recompute
+ item->textRect.w = 0;
+ item->textRect.h = 0;
+}
+
+// FIXME: consolidate this with nearby stuff
+void Item_UpdatePosition(itemDef_t *item) {
+ float x, y;
+ menuDef_t *menu;
+
+ if (item == NULL || item->parent == NULL) {
+ return;
+ }
+
+ menu = item->parent;
+
+ x = menu->window.rect.x;
+ y = menu->window.rect.y;
+
+ if (menu->window.border != 0) {
+ x += menu->window.borderSize;
+ y += menu->window.borderSize;
+ }
+
+ Item_SetScreenCoords(item, x, y);
+
+}
+
+// menus
+void Menu_UpdatePosition(menuDef_t *menu) {
+ int i;
+ float x, y;
+
+ if (menu == NULL) {
+ return;
+ }
+
+ x = menu->window.rect.x;
+ y = menu->window.rect.y;
+ if (menu->window.border != 0) {
+ x += menu->window.borderSize;
+ y += menu->window.borderSize;
+ }
+
+ for (i = 0; i < menu->itemCount; i++) {
+ Item_SetScreenCoords(menu->items[i], x, y);
+ }
+}
+
+void Menu_PostParse(menuDef_t *menu) {
+ if (menu == NULL) {
+ return;
+ }
+ if (menu->fullScreen) {
+ menu->window.rect.x = 0;
+ menu->window.rect.y = 0;
+ menu->window.rect.w = 640;
+ menu->window.rect.h = 480;
+ }
+ Menu_UpdatePosition(menu);
+}
+
+itemDef_t *Menu_ClearFocus(menuDef_t *menu) {
+ int i;
+ itemDef_t *ret = NULL;
+
+ if (menu == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->window.flags & WINDOW_HASFOCUS) {
+ ret = menu->items[i];
+ }
+ menu->items[i]->window.flags &= ~WINDOW_HASFOCUS;
+ if (menu->items[i]->leaveFocus) {
+ Item_RunScript(menu->items[i], menu->items[i]->leaveFocus);
+ }
+ }
+
+ return ret;
+}
+
+qboolean IsVisible(int flags) {
+ return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT));
+}
+
+qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) {
+ if (rect) {
+ if (x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h) {
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name) {
+ int i;
+ int count = 0;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name) {
+ int i;
+ int count = 0;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) {
+ if (count == index) {
+ return menu->items[i];
+ }
+ count++;
+ }
+ }
+ return NULL;
+}
+
+
+
+void Script_SetColor(itemDef_t *item, char **args) {
+ const char *name;
+ int i;
+ float f;
+ vec4_t *out;
+ // expecting type of color to set and 4 args for the color
+ if (String_Parse(args, &name)) {
+ out = NULL;
+ if (Q_stricmp(name, "backcolor") == 0) {
+ out = &item->window.backColor;
+ item->window.flags |= WINDOW_BACKCOLORSET;
+ } else if (Q_stricmp(name, "forecolor") == 0) {
+ out = &item->window.foreColor;
+ item->window.flags |= WINDOW_FORECOLORSET;
+ } else if (Q_stricmp(name, "bordercolor") == 0) {
+ out = &item->window.borderColor;
+ }
+
+ if (out) {
+ for (i = 0; i < 4; i++) {
+ if (!Float_Parse(args, &f)) {
+ return;
+ }
+ (*out)[i] = f;
+ }
+ }
+ }
+}
+
+void Script_SetAsset(itemDef_t *item, char **args) {
+ const char *name;
+ // expecting name to set asset to
+ if (String_Parse(args, &name)) {
+ // check for a model
+ if (item->type == ITEM_TYPE_MODEL) {
+ }
+ }
+}
+
+void Script_SetBackground(itemDef_t *item, char **args) {
+ const char *name;
+ // expecting name to set asset to
+ if (String_Parse(args, &name)) {
+ item->window.background = DC->registerShaderNoMip(name);
+ }
+}
+
+
+
+
+itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p) {
+ int i;
+ if (menu == NULL || p == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Q_stricmp(p, menu->items[i]->window.name) == 0) {
+ return menu->items[i];
+ }
+ }
+
+ return NULL;
+}
+
+void Script_SetTeamColor(itemDef_t *item, char **args) {
+ if (DC->getTeamColor) {
+ int i;
+ vec4_t color;
+ DC->getTeamColor(&color);
+ for (i = 0; i < 4; i++) {
+ item->window.backColor[i] = color[i];
+ }
+ }
+}
+
+void Script_SetItemColor(itemDef_t *item, char **args) {
+ const char *itemname;
+ const char *name;
+ vec4_t color;
+ int i;
+ vec4_t *out;
+ // expecting type of color to set and 4 args for the color
+ if (String_Parse(args, &itemname) && String_Parse(args, &name)) {
+ itemDef_t *item2;
+ int j;
+ int count = Menu_ItemsMatchingGroup(item->parent, itemname);
+
+ if (!Color_Parse(args, &color)) {
+ return;
+ }
+
+ for (j = 0; j < count; j++) {
+ item2 = Menu_GetMatchingItemByNumber(item->parent, j, itemname);
+ if (item2 != NULL) {
+ out = NULL;
+ if (Q_stricmp(name, "backcolor") == 0) {
+ out = &item2->window.backColor;
+ } else if (Q_stricmp(name, "forecolor") == 0) {
+ out = &item2->window.foreColor;
+ item2->window.flags |= WINDOW_FORECOLORSET;
+ } else if (Q_stricmp(name, "bordercolor") == 0) {
+ out = &item2->window.borderColor;
+ }
+
+ if (out) {
+ for (i = 0; i < 4; i++) {
+ (*out)[i] = color[i];
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ if (bShow) {
+ item->window.flags |= WINDOW_VISIBLE;
+ } else {
+ item->window.flags &= ~WINDOW_VISIBLE;
+ // stop cinematics playing in the window
+ if (item->window.cinematic >= 0) {
+ DC->stopCinematic(item->window.cinematic);
+ item->window.cinematic = -1;
+ }
+ }
+ }
+ }
+}
+
+void Menu_FadeItemByName(menuDef_t *menu, const char *p, qboolean fadeOut) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ if (fadeOut) {
+ item->window.flags |= (WINDOW_FADINGOUT | WINDOW_VISIBLE);
+ item->window.flags &= ~WINDOW_FADINGIN;
+ } else {
+ item->window.flags |= (WINDOW_VISIBLE | WINDOW_FADINGIN);
+ item->window.flags &= ~WINDOW_FADINGOUT;
+ }
+ }
+ }
+}
+
+menuDef_t *Menus_FindByName(const char *p) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ if (Q_stricmp(Menus[i].window.name, p) == 0) {
+ return &Menus[i];
+ }
+ }
+ return NULL;
+}
+
+void Menus_ShowByName(const char *p) {
+ menuDef_t *menu = Menus_FindByName(p);
+ if (menu) {
+ Menus_Activate(menu);
+ }
+}
+
+void Menus_OpenByName(const char *p) {
+ Menus_ActivateByName(p);
+}
+
+static void Menu_RunCloseScript(menuDef_t *menu) {
+ if (menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose) {
+ itemDef_t item;
+ item.parent = menu;
+ Item_RunScript(&item, menu->onClose);
+ }
+}
+
+void Menus_CloseByName(const char *p) {
+ menuDef_t *menu = Menus_FindByName(p);
+ if (menu != NULL) {
+ Menu_RunCloseScript(menu);
+ menu->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS);
+ }
+}
+
+void Menus_CloseAll( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ Menu_RunCloseScript(&Menus[i]);
+ Menus[i].window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ }
+
+ g_editingField = qfalse;
+ g_waitingForKey = qfalse;
+}
+
+
+void Script_Show(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_ShowItemByName(item->parent, name, qtrue);
+ }
+}
+
+void Script_Hide(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_ShowItemByName(item->parent, name, qfalse);
+ }
+}
+
+void Script_FadeIn(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_FadeItemByName(item->parent, name, qfalse);
+ }
+}
+
+void Script_FadeOut(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_FadeItemByName(item->parent, name, qtrue);
+ }
+}
+
+
+
+void Script_Open(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menus_OpenByName(name);
+ }
+}
+
+void Script_ConditionalOpen(itemDef_t *item, char **args) {
+ const char *cvar;
+ const char *name1;
+ const char *name2;
+ float val;
+
+ if ( String_Parse(args, &cvar) && String_Parse(args, &name1) && String_Parse(args, &name2) ) {
+ val = DC->getCVarValue( cvar );
+ if ( val == 0.f ) {
+ Menus_OpenByName(name2);
+ } else {
+ Menus_OpenByName(name1);
+ }
+ }
+}
+
+void Script_Close(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menus_CloseByName(name);
+ }
+}
+
+void Menu_TransitionItemByName(menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ item->window.flags |= (WINDOW_INTRANSITION | WINDOW_VISIBLE);
+ item->window.offsetTime = time;
+ memcpy(&item->window.rectClient, &rectFrom, sizeof(rectDef_t));
+ memcpy(&item->window.rectEffects, &rectTo, sizeof(rectDef_t));
+ item->window.rectEffects2.x = fabs(rectTo.x - rectFrom.x) / amt;
+ item->window.rectEffects2.y = fabs(rectTo.y - rectFrom.y) / amt;
+ item->window.rectEffects2.w = fabs(rectTo.w - rectFrom.w) / amt;
+ item->window.rectEffects2.h = fabs(rectTo.h - rectFrom.h) / amt;
+ Item_UpdatePosition(item);
+ }
+ }
+}
+
+
+void Script_Transition(itemDef_t *item, char **args) {
+ const char *name;
+ rectDef_t rectFrom, rectTo;
+ int time;
+ float amt;
+
+ if (String_Parse(args, &name)) {
+ if ( Rect_Parse(args, &rectFrom) && Rect_Parse(args, &rectTo) && Int_Parse(args, &time) && Float_Parse(args, &amt)) {
+ Menu_TransitionItemByName(item->parent, name, rectFrom, rectTo, time, amt);
+ }
+ }
+}
+
+
+void Menu_OrbitItemByName(menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ item->window.flags |= (WINDOW_ORBITING | WINDOW_VISIBLE);
+ item->window.offsetTime = time;
+ item->window.rectEffects.x = cx;
+ item->window.rectEffects.y = cy;
+ item->window.rectClient.x = x;
+ item->window.rectClient.y = y;
+ Item_UpdatePosition(item);
+ }
+ }
+}
+
+
+void Script_Orbit(itemDef_t *item, char **args) {
+ const char *name;
+ float cx, cy, x, y;
+ int time;
+
+ if (String_Parse(args, &name)) {
+ if ( Float_Parse(args, &x) && Float_Parse(args, &y) && Float_Parse(args, &cx) && Float_Parse(args, &cy) && Int_Parse(args, &time) ) {
+ Menu_OrbitItemByName(item->parent, name, x, y, cx, cy, time);
+ }
+ }
+}
+
+
+
+void Script_SetFocus(itemDef_t *item, char **args) {
+ const char *name;
+ itemDef_t *focusItem;
+
+ if (String_Parse(args, &name)) {
+ focusItem = Menu_FindItemByName(item->parent, name);
+ if (focusItem && !(focusItem->window.flags & WINDOW_DECORATION)) {
+ Menu_ClearFocus(item->parent);
+ focusItem->window.flags |= WINDOW_HASFOCUS;
+ if (focusItem->onFocus) {
+ Item_RunScript(focusItem, focusItem->onFocus);
+ }
+ if (focusItem->type == ITEM_TYPE_EDITFIELD || focusItem->type == ITEM_TYPE_SAYFIELD || focusItem->type == ITEM_TYPE_NUMERICFIELD) {
+ focusItem->cursorPos = 0;
+ g_editingField = qtrue;
+ g_editItem = focusItem;
+ if (focusItem->type == ITEM_TYPE_SAYFIELD) {
+ DC->setOverstrikeMode(qfalse);
+ }
+ }
+ if (DC->Assets.itemFocusSound) {
+ DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND );
+ }
+ }
+ }
+}
+
+void Script_SetPlayerModel(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ DC->setCVar("team_model", name);
+ }
+}
+
+void Script_SetPlayerHead(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ DC->setCVar("team_headmodel", name);
+ }
+}
+
+void Script_SetCvar(itemDef_t *item, char **args) {
+ const char *cvar, *val;
+ if (String_Parse(args, &cvar) && String_Parse(args, &val)) {
+ DC->setCVar(cvar, val);
+ }
+
+}
+
+void Script_Exec(itemDef_t *item, char **args) {
+ const char *val;
+ if (String_Parse(args, &val)) {
+ DC->executeText(EXEC_APPEND, va("%s ; ", val));
+ }
+}
+
+void Script_Play(itemDef_t *item, char **args) {
+ const char *val;
+ if (String_Parse(args, &val)) {
+ DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_LOCAL_SOUND);
+ }
+}
+
+void Script_playLooped(itemDef_t *item, char **args) {
+ const char *val;
+ if (String_Parse(args, &val)) {
+ DC->stopBackgroundTrack();
+ DC->startBackgroundTrack(val, val);
+ }
+}
+
+
+commandDef_t commandList[] =
+{
+ {"fadein", &Script_FadeIn}, // group/name
+ {"fadeout", &Script_FadeOut}, // group/name
+ {"show", &Script_Show}, // group/name
+ {"hide", &Script_Hide}, // group/name
+ {"setcolor", &Script_SetColor}, // works on this
+ {"open", &Script_Open}, // menu
+ {"conditionalopen", &Script_ConditionalOpen}, // menu
+ {"close", &Script_Close}, // menu
+ {"setasset", &Script_SetAsset}, // works on this
+ {"setbackground", &Script_SetBackground}, // works on this
+ {"setitemcolor", &Script_SetItemColor}, // group/name
+ {"setteamcolor", &Script_SetTeamColor}, // sets this background color to team color
+ {"setfocus", &Script_SetFocus}, // sets this background color to team color
+ {"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color
+ {"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color
+ {"transition", &Script_Transition}, // group/name
+ {"setcvar", &Script_SetCvar}, // group/name
+ {"exec", &Script_Exec}, // group/name
+ {"play", &Script_Play}, // group/name
+ {"playlooped", &Script_playLooped}, // group/name
+ {"orbit", &Script_Orbit} // group/name
+};
+
+int scriptCommandCount = sizeof(commandList) / sizeof(commandDef_t);
+
+
+void Item_RunScript(itemDef_t *item, const char *s) {
+ char script[1024], *p;
+ int i;
+ qboolean bRan;
+ memset(script, 0, sizeof(script));
+ if (item && s && s[0]) {
+ Q_strcat(script, 1024, s);
+ p = script;
+ while (1) {
+ const char *command;
+ // expect command then arguments, ; ends command, NULL ends script
+ if (!String_Parse(&p, &command)) {
+ return;
+ }
+
+ if (command[0] == ';' && command[1] == '\0') {
+ continue;
+ }
+
+ bRan = qfalse;
+ for (i = 0; i < scriptCommandCount; i++) {
+ if (Q_stricmp(command, commandList[i].name) == 0) {
+ (commandList[i].handler(item, &p));
+ bRan = qtrue;
+ break;
+ }
+ }
+ // not in our auto list, pass to handler
+ if (!bRan) {
+ DC->runScript(&p);
+ }
+ }
+ }
+}
+
+
+qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) {
+ char script[1024], *p;
+ memset(script, 0, sizeof(script));
+ if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) {
+ char buff[1024];
+ DC->getCVarString(item->cvarTest, buff, sizeof(buff));
+
+ Q_strcat(script, 1024, item->enableCvar);
+ p = script;
+ while (1) {
+ const char *val;
+ // expect value then ; or NULL, NULL ends list
+ if (!String_Parse(&p, &val)) {
+ return (item->cvarFlags & flag) ? qfalse : qtrue;
+ }
+
+ if (val[0] == ';' && val[1] == '\0') {
+ continue;
+ }
+
+ // enable it if any of the values are true
+ if (item->cvarFlags & flag) {
+ if (Q_stricmp(buff, val) == 0) {
+ return qtrue;
+ }
+ } else {
+ // disable it if any of the values are true
+ if (Q_stricmp(buff, val) == 0) {
+ return qfalse;
+ }
+ }
+
+ }
+ return (item->cvarFlags & flag) ? qfalse : qtrue;
+ }
+ return qtrue;
+}
+
+
+// will optionaly set focus to this item
+qboolean Item_SetFocus(itemDef_t *item, float x, float y) {
+ int i;
+ itemDef_t *oldFocus;
+ sfxHandle_t *sfx = &DC->Assets.itemFocusSound;
+ qboolean playSound = qfalse;
+ menuDef_t *parent; // bk001206: = (menuDef_t*)item->parent;
+ // sanity check, non-null, not a decoration and does not already have the focus
+ if (item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS || !(item->window.flags & WINDOW_VISIBLE)) {
+ return qfalse;
+ }
+
+ // bk001206 - this can be NULL.
+ parent = (menuDef_t*)item->parent;
+
+ // items can be enabled and disabled based on cvars
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ return qfalse;
+ }
+
+ if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) {
+ return qfalse;
+ }
+
+ oldFocus = Menu_ClearFocus(item->parent);
+
+ if (item->type == ITEM_TYPE_TEXT) {
+ rectDef_t r;
+ r = item->textRect;
+ r.y -= r.h;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ item->window.flags |= WINDOW_HASFOCUS;
+ if (item->focusSound) {
+ sfx = &item->focusSound;
+ }
+ playSound = qtrue;
+ } else {
+ if (oldFocus) {
+ oldFocus->window.flags |= WINDOW_HASFOCUS;
+ if (oldFocus->onFocus) {
+ Item_RunScript(oldFocus, oldFocus->onFocus);
+ }
+ }
+ }
+ } else {
+ item->window.flags |= WINDOW_HASFOCUS;
+ if (item->onFocus) {
+ Item_RunScript(item, item->onFocus);
+ }
+ if (item->focusSound) {
+ sfx = &item->focusSound;
+ }
+ playSound = qtrue;
+ }
+
+ if (playSound && sfx) {
+ DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND );
+ }
+
+ for (i = 0; i < parent->itemCount; i++) {
+ if (parent->items[i] == item) {
+ parent->cursorItem = i;
+ break;
+ }
+ }
+
+ return qtrue;
+}
+
+int Item_ListBox_MaxScroll(itemDef_t *item) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+ int count = DC->feederCount(item->special);
+ int max;
+
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ max = count - (item->window.rect.w / listPtr->elementWidth) + 1;
+ }
+ else {
+ max = count - (item->window.rect.h / listPtr->elementHeight) + 1;
+ }
+ if (max < 0) {
+ return 0;
+ }
+ return max;
+}
+
+int Item_ListBox_ThumbPosition(itemDef_t *item) {
+ float max, pos, size;
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+
+ max = Item_ListBox_MaxScroll(item);
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ size = item->window.rect.w - (SCROLLBAR_SIZE * 2) - 2;
+ if (max > 0) {
+ pos = (size-SCROLLBAR_SIZE) / (float) max;
+ } else {
+ pos = 0;
+ }
+ pos *= listPtr->startPos;
+ return item->window.rect.x + 1 + SCROLLBAR_SIZE + pos;
+ }
+ else {
+ size = item->window.rect.h - (SCROLLBAR_SIZE * 2) - 2;
+ if (max > 0) {
+ pos = (size-SCROLLBAR_SIZE) / (float) max;
+ } else {
+ pos = 0;
+ }
+ pos *= listPtr->startPos;
+ return item->window.rect.y + 1 + SCROLLBAR_SIZE + pos;
+ }
+}
+
+int Item_ListBox_ThumbDrawPosition(itemDef_t *item) {
+ int min, max;
+
+ if (itemCapture == item) {
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ min = item->window.rect.x + SCROLLBAR_SIZE + 1;
+ max = item->window.rect.x + item->window.rect.w - 2*SCROLLBAR_SIZE - 1;
+ if (DC->cursorx >= min + SCROLLBAR_SIZE/2 && DC->cursorx <= max + SCROLLBAR_SIZE/2) {
+ return DC->cursorx - SCROLLBAR_SIZE/2;
+ }
+ else {
+ return Item_ListBox_ThumbPosition(item);
+ }
+ }
+ else {
+ min = item->window.rect.y + SCROLLBAR_SIZE + 1;
+ max = item->window.rect.y + item->window.rect.h - 2*SCROLLBAR_SIZE - 1;
+ if (DC->cursory >= min + SCROLLBAR_SIZE/2 && DC->cursory <= max + SCROLLBAR_SIZE/2) {
+ return DC->cursory - SCROLLBAR_SIZE/2;
+ }
+ else {
+ return Item_ListBox_ThumbPosition(item);
+ }
+ }
+ }
+ else {
+ return Item_ListBox_ThumbPosition(item);
+ }
+}
+
+float Item_Slider_ThumbPosition(itemDef_t *item) {
+ float value, range, x;
+ editFieldDef_t *editDef = item->typeData;
+
+ if (item->text) {
+ x = item->textRect.x + item->textRect.w + 8;
+ } else {
+ x = item->window.rect.x;
+ }
+
+ if (editDef == NULL && item->cvar) {
+ return x;
+ }
+
+ value = DC->getCVarValue(item->cvar);
+
+ if (value < editDef->minVal) {
+ value = editDef->minVal;
+ } else if (value > editDef->maxVal) {
+ value = editDef->maxVal;
+ }
+
+ range = editDef->maxVal - editDef->minVal;
+ value -= editDef->minVal;
+ value /= range;
+ //value /= (editDef->maxVal - editDef->minVal);
+ value *= SLIDER_WIDTH;
+ x += value;
+ // vm fuckage
+ //x = x + (((float)value / editDef->maxVal) * SLIDER_WIDTH);
+ return x;
+}
+
+int Item_Slider_OverSlider(itemDef_t *item, float x, float y) {
+ rectDef_t r;
+
+ r.x = Item_Slider_ThumbPosition(item) - (SLIDER_THUMB_WIDTH / 2);
+ r.y = item->window.rect.y - 2;
+ r.w = SLIDER_THUMB_WIDTH;
+ r.h = SLIDER_THUMB_HEIGHT;
+
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_THUMB;
+ }
+ return 0;
+}
+
+int Item_ListBox_OverLB(itemDef_t *item, float x, float y) {
+ rectDef_t r;
+ listBoxDef_t *listPtr;
+ int thumbstart;
+ int count;
+
+ count = DC->feederCount(item->special);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ // check if on left arrow
+ r.x = item->window.rect.x;
+ r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE;
+ r.h = r.w = SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_LEFTARROW;
+ }
+ // check if on right arrow
+ r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_RIGHTARROW;
+ }
+ // check if on thumb
+ thumbstart = Item_ListBox_ThumbPosition(item);
+ r.x = thumbstart;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_THUMB;
+ }
+ r.x = item->window.rect.x + SCROLLBAR_SIZE;
+ r.w = thumbstart - r.x;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGUP;
+ }
+ r.x = thumbstart + SCROLLBAR_SIZE;
+ r.w = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGDN;
+ }
+ } else {
+ r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE;
+ r.y = item->window.rect.y;
+ r.h = r.w = SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_LEFTARROW;
+ }
+ r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_RIGHTARROW;
+ }
+ thumbstart = Item_ListBox_ThumbPosition(item);
+ r.y = thumbstart;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_THUMB;
+ }
+ r.y = item->window.rect.y + SCROLLBAR_SIZE;
+ r.h = thumbstart - r.y;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGUP;
+ }
+ r.y = thumbstart + SCROLLBAR_SIZE;
+ r.h = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGDN;
+ }
+ }
+ return 0;
+}
+
+
+void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y)
+{
+ rectDef_t r;
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+
+ item->window.flags &= ~(WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN);
+ item->window.flags |= Item_ListBox_OverLB(item, x, y);
+
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) {
+ // check for selection hit as we have exausted buttons and thumb
+ if (listPtr->elementStyle == LISTBOX_IMAGE) {
+ r.x = item->window.rect.x;
+ r.y = item->window.rect.y;
+ r.h = item->window.rect.h - SCROLLBAR_SIZE;
+ r.w = item->window.rect.w - listPtr->drawPadding;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ listPtr->cursorPos = (int)((x - r.x) / listPtr->elementWidth) + listPtr->startPos;
+ if (listPtr->cursorPos >= listPtr->endPos) {
+ listPtr->cursorPos = listPtr->endPos;
+ }
+ }
+ } else {
+ // text hit..
+ }
+ }
+ } else if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) {
+ r.x = item->window.rect.x;
+ r.y = item->window.rect.y;
+ r.w = item->window.rect.w - SCROLLBAR_SIZE;
+ r.h = item->window.rect.h - listPtr->drawPadding;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ listPtr->cursorPos = (int)((y - 2 - r.y) / listPtr->elementHeight) + listPtr->startPos;
+ if (listPtr->cursorPos > listPtr->endPos) {
+ listPtr->cursorPos = listPtr->endPos;
+ }
+ }
+ }
+}
+
+void Item_MouseEnter(itemDef_t *item, float x, float y) {
+ rectDef_t r;
+ if (item) {
+ r = item->textRect;
+ r.y -= r.h;
+ // in the text rect?
+
+ // items can be enabled and disabled based on cvars
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ return;
+ }
+
+ if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) {
+ return;
+ }
+
+ if (Rect_ContainsPoint(&r, x, y)) {
+ if (!(item->window.flags & WINDOW_MOUSEOVERTEXT)) {
+ Item_RunScript(item, item->mouseEnterText);
+ item->window.flags |= WINDOW_MOUSEOVERTEXT;
+ }
+ if (!(item->window.flags & WINDOW_MOUSEOVER)) {
+ Item_RunScript(item, item->mouseEnter);
+ item->window.flags |= WINDOW_MOUSEOVER;
+ }
+
+ } else {
+ // not in the text rect
+ if (item->window.flags & WINDOW_MOUSEOVERTEXT) {
+ // if we were
+ Item_RunScript(item, item->mouseExitText);
+ item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
+ }
+ if (!(item->window.flags & WINDOW_MOUSEOVER)) {
+ Item_RunScript(item, item->mouseEnter);
+ item->window.flags |= WINDOW_MOUSEOVER;
+ }
+
+ if (item->type == ITEM_TYPE_LISTBOX) {
+ Item_ListBox_MouseEnter(item, x, y);
+ }
+ }
+ }
+}
+
+void Item_MouseLeave(itemDef_t *item) {
+ if (item) {
+ if (item->window.flags & WINDOW_MOUSEOVERTEXT) {
+ Item_RunScript(item, item->mouseExitText);
+ item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
+ }
+ Item_RunScript(item, item->mouseExit);
+ item->window.flags &= ~(WINDOW_LB_RIGHTARROW | WINDOW_LB_LEFTARROW);
+ }
+}
+
+itemDef_t *Menu_HitTest(menuDef_t *menu, float x, float y) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) {
+ return menu->items[i];
+ }
+ }
+ return NULL;
+}
+
+void Item_SetMouseOver(itemDef_t *item, qboolean focus) {
+ if (item) {
+ if (focus) {
+ item->window.flags |= WINDOW_MOUSEOVER;
+ } else {
+ item->window.flags &= ~WINDOW_MOUSEOVER;
+ }
+ }
+}
+
+
+qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key) {
+ if (item && DC->ownerDrawHandleKey) {
+ return DC->ownerDrawHandleKey(item->window.ownerDraw, item->window.ownerDrawFlags, &item->special, key);
+ }
+ return qfalse;
+}
+
+qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+ int count = DC->feederCount(item->special);
+ int max, viewmax;
+
+ if (force || (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) {
+ max = Item_ListBox_MaxScroll(item);
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ viewmax = (item->window.rect.w / listPtr->elementWidth);
+ if ( key == K_LEFTARROW || key == K_KP_LEFTARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos--;
+ if (listPtr->cursorPos < 0) {
+ listPtr->cursorPos = 0;
+ }
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0)
+ listPtr->startPos = 0;
+ }
+ return qtrue;
+ }
+ if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos++;
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= count) {
+ listPtr->cursorPos = count-1;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos++;
+ if (listPtr->startPos >= count)
+ listPtr->startPos = count-1;
+ }
+ return qtrue;
+ }
+ }
+ else {
+ viewmax = (item->window.rect.h / listPtr->elementHeight);
+ if ( key == K_UPARROW || key == K_KP_UPARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos--;
+ if (listPtr->cursorPos < 0) {
+ listPtr->cursorPos = 0;
+ }
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0)
+ listPtr->startPos = 0;
+ }
+ return qtrue;
+ }
+ if ( key == K_DOWNARROW || key == K_KP_DOWNARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos++;
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= count) {
+ listPtr->cursorPos = count-1;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos++;
+ if (listPtr->startPos > max)
+ listPtr->startPos = max;
+ }
+ return qtrue;
+ }
+ }
+ // mouse hit
+ if (key == K_MOUSE1 || key == K_MOUSE2) {
+ if (item->window.flags & WINDOW_LB_LEFTARROW) {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ } else if (item->window.flags & WINDOW_LB_RIGHTARROW) {
+ // one down
+ listPtr->startPos++;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ } else if (item->window.flags & WINDOW_LB_PGUP) {
+ // page up
+ listPtr->startPos -= viewmax;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ } else if (item->window.flags & WINDOW_LB_PGDN) {
+ // page down
+ listPtr->startPos += viewmax;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ } else if (item->window.flags & WINDOW_LB_THUMB) {
+ // Display_SetCaptureItem(item);
+ } else {
+ // select an item
+ if (DC->realTime < lastListBoxClickTime && listPtr->doubleClick) {
+ Item_RunScript(item, listPtr->doubleClick);
+ }
+ lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY;
+ if (item->cursorPos != listPtr->cursorPos) {
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ }
+ return qtrue;
+ }
+
+ // Scroll wheel
+ if (key == K_MWHEELUP) {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ return qtrue;
+ }
+ if (key == K_MWHEELDOWN) {
+ listPtr->startPos++;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ return qtrue;
+ }
+
+ // Invoke the doubleClick handler when enter is pressed
+ if( key == K_ENTER )
+ {
+ if( listPtr->doubleClick )
+ Item_RunScript( item, listPtr->doubleClick );
+
+ return qtrue;
+ }
+
+ if ( key == K_HOME || key == K_KP_HOME) {
+ // home
+ listPtr->startPos = 0;
+ return qtrue;
+ }
+ if ( key == K_END || key == K_KP_END) {
+ // end
+ listPtr->startPos = max;
+ return qtrue;
+ }
+ if (key == K_PGUP || key == K_KP_PGUP ) {
+ // page up
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos -= viewmax;
+ if (listPtr->cursorPos < 0) {
+ listPtr->cursorPos = 0;
+ }
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos -= viewmax;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ }
+ return qtrue;
+ }
+ if ( key == K_PGDN || key == K_KP_PGDN ) {
+ // page down
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos += viewmax;
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= count) {
+ listPtr->cursorPos = count-1;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos += viewmax;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ }
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+qboolean Item_YesNo_HandleKey(itemDef_t *item, int key) {
+
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) {
+ if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) {
+ DC->setCVar(item->cvar, va("%i", !DC->getCVarValue(item->cvar)));
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+
+}
+
+int Item_Multi_CountSettings(itemDef_t *item) {
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr == NULL) {
+ return 0;
+ }
+ return multiPtr->count;
+}
+
+int Item_Multi_FindCvarByValue(itemDef_t *item) {
+ char buff[1024];
+ float value = 0;
+ int i;
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr) {
+ if (multiPtr->strDef) {
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ } else {
+ value = DC->getCVarValue(item->cvar);
+ }
+ for (i = 0; i < multiPtr->count; i++) {
+ if (multiPtr->strDef) {
+ if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) {
+ return i;
+ }
+ } else {
+ if (multiPtr->cvarValue[i] == value) {
+ return i;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+const char *Item_Multi_Setting(itemDef_t *item) {
+ char buff[1024];
+ float value = 0;
+ int i;
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr) {
+ if (multiPtr->strDef) {
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ } else {
+ value = DC->getCVarValue(item->cvar);
+ }
+ for (i = 0; i < multiPtr->count; i++) {
+ if (multiPtr->strDef) {
+ if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) {
+ return multiPtr->cvarList[i];
+ }
+ } else {
+ if (multiPtr->cvarValue[i] == value) {
+ return multiPtr->cvarList[i];
+ }
+ }
+ }
+ }
+ return "";
+}
+
+qboolean Item_Multi_HandleKey(itemDef_t *item, int key) {
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr) {
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) {
+ if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) {
+ int current = Item_Multi_FindCvarByValue(item) + 1;
+ int max = Item_Multi_CountSettings(item);
+ if ( current < 0 || current >= max ) {
+ current = 0;
+ }
+ if (multiPtr->strDef) {
+ DC->setCVar(item->cvar, multiPtr->cvarStr[current]);
+ } else {
+ float value = multiPtr->cvarValue[current];
+ if (((float)((int) value)) == value) {
+ DC->setCVar(item->cvar, va("%i", (int) value ));
+ }
+ else {
+ DC->setCVar(item->cvar, va("%f", value ));
+ }
+ }
+ return qtrue;
+ }
+ }
+ }
+ return qfalse;
+}
+
+qboolean Item_TextField_HandleKey(itemDef_t *item, int key) {
+ char buff[1024];
+ int len;
+ itemDef_t *newItem = NULL;
+ editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData;
+
+ if (item->cvar) {
+
+ memset(buff, 0, sizeof(buff));
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ len = strlen(buff);
+ if (editPtr->maxChars && len > editPtr->maxChars) {
+ len = editPtr->maxChars;
+ }
+ if ( key & K_CHAR_FLAG ) {
+ key &= ~K_CHAR_FLAG;
+
+
+ if (key == 'h' - 'a' + 1 ) { // ctrl-h is backspace
+ if ( item->cursorPos > 0 ) {
+ memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos);
+ item->cursorPos--;
+ if (item->cursorPos < editPtr->paintOffset) {
+ editPtr->paintOffset--;
+ }
+ }
+ DC->setCVar(item->cvar, buff);
+ return qtrue;
+ }
+
+
+ //
+ // ignore any non printable chars
+ //
+ if ( key < 32 || !item->cvar) {
+ return qtrue;
+ }
+
+ if (item->type == ITEM_TYPE_NUMERICFIELD) {
+ if (key < '0' || key > '9') {
+ return qfalse;
+ }
+ }
+
+ if (!DC->getOverstrikeMode()) {
+ if (( len == MAX_EDITFIELD - 1 ) || (editPtr->maxChars && len >= editPtr->maxChars)) {
+ return qtrue;
+ }
+ memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos );
+ } else {
+ if (editPtr->maxChars && item->cursorPos >= editPtr->maxChars) {
+ return qtrue;
+ }
+ }
+
+ buff[item->cursorPos] = key;
+
+ DC->setCVar(item->cvar, buff);
+
+ if (item->cursorPos < len + 1) {
+ item->cursorPos++;
+ if (editPtr->maxPaintChars && item->cursorPos > editPtr->maxPaintChars) {
+ editPtr->paintOffset++;
+ }
+ }
+
+ } else {
+
+ if ( key == K_DEL || key == K_KP_DEL ) {
+ if ( item->cursorPos < len ) {
+ memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos);
+ DC->setCVar(item->cvar, buff);
+ }
+ return qtrue;
+ }
+
+ if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW )
+ {
+ if (editPtr->maxPaintChars && item->cursorPos >= editPtr->maxPaintChars && item->cursorPos < len) {
+ item->cursorPos++;
+ editPtr->paintOffset++;
+ return qtrue;
+ }
+ if (item->cursorPos < len) {
+ item->cursorPos++;
+ }
+ return qtrue;
+ }
+
+ if ( key == K_LEFTARROW || key == K_KP_LEFTARROW )
+ {
+ if ( item->cursorPos > 0 ) {
+ item->cursorPos--;
+ }
+ if (item->cursorPos < editPtr->paintOffset) {
+ editPtr->paintOffset--;
+ }
+ return qtrue;
+ }
+
+ if ( key == K_HOME || key == K_KP_HOME) {// || ( tolower(key) == 'a' && trap_Key_IsDown( K_CTRL ) ) ) {
+ item->cursorPos = 0;
+ editPtr->paintOffset = 0;
+ return qtrue;
+ }
+
+ if ( key == K_END || key == K_KP_END) {// ( tolower(key) == 'e' && trap_Key_IsDown( K_CTRL ) ) ) {
+ item->cursorPos = len;
+ if(item->cursorPos > editPtr->maxPaintChars) {
+ editPtr->paintOffset = len - editPtr->maxPaintChars;
+ }
+ return qtrue;
+ }
+
+ if ( key == K_INS || key == K_KP_INS ) {
+ DC->setOverstrikeMode(!DC->getOverstrikeMode());
+ return qtrue;
+ }
+ }
+
+ if (key == K_TAB || key == K_DOWNARROW || key == K_KP_DOWNARROW) {
+ if (item->type == ITEM_TYPE_SAYFIELD) {
+ return qtrue;
+ }
+
+ newItem = Menu_SetNextCursorItem(item->parent);
+ if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_SAYFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) {
+ g_editItem = newItem;
+ }
+ }
+
+ if (key == K_UPARROW || key == K_KP_UPARROW) {
+ if (item->type == ITEM_TYPE_SAYFIELD) {
+ return qtrue;
+ }
+
+ newItem = Menu_SetPrevCursorItem(item->parent);
+ if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_SAYFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) {
+ g_editItem = newItem;
+ }
+ }
+
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 || key == K_MOUSE4) {
+ if (item->type == ITEM_TYPE_SAYFIELD) {
+ return qtrue;
+ }
+ return qfalse;
+ }
+
+ if ( key == K_ENTER || key == K_KP_ENTER || key == K_ESCAPE) {
+ return qfalse;
+ }
+
+ return qtrue;
+ }
+ return qfalse;
+
+}
+
+static void Scroll_ListBox_AutoFunc(void *p) {
+ scrollInfo_t *si = (scrollInfo_t*)p;
+ if (DC->realTime > si->nextScrollTime) {
+ // need to scroll which is done by simulating a click to the item
+ // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
+ // so it calls it directly
+ Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse);
+ si->nextScrollTime = DC->realTime + si->adjustValue;
+ }
+
+ if (DC->realTime > si->nextAdjustTime) {
+ si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+ if (si->adjustValue > SCROLL_TIME_FLOOR) {
+ si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
+ }
+ }
+}
+
+static void Scroll_ListBox_ThumbFunc(void *p) {
+ scrollInfo_t *si = (scrollInfo_t*)p;
+ rectDef_t r;
+ int pos, max;
+
+ listBoxDef_t *listPtr = (listBoxDef_t*)si->item->typeData;
+ if (si->item->window.flags & WINDOW_HORIZONTAL) {
+ if (DC->cursorx == si->xStart) {
+ return;
+ }
+ r.x = si->item->window.rect.x + SCROLLBAR_SIZE + 1;
+ r.y = si->item->window.rect.y + si->item->window.rect.h - SCROLLBAR_SIZE - 1;
+ r.h = SCROLLBAR_SIZE;
+ r.w = si->item->window.rect.w - (SCROLLBAR_SIZE*2) - 2;
+ max = Item_ListBox_MaxScroll(si->item);
+ //
+ pos = (DC->cursorx - r.x - SCROLLBAR_SIZE/2) * max / (r.w - SCROLLBAR_SIZE);
+ if (pos < 0) {
+ pos = 0;
+ }
+ else if (pos > max) {
+ pos = max;
+ }
+ listPtr->startPos = pos;
+ si->xStart = DC->cursorx;
+ }
+ else if (DC->cursory != si->yStart) {
+
+ r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_SIZE - 1;
+ r.y = si->item->window.rect.y + SCROLLBAR_SIZE + 1;
+ r.h = si->item->window.rect.h - (SCROLLBAR_SIZE*2) - 2;
+ r.w = SCROLLBAR_SIZE;
+ max = Item_ListBox_MaxScroll(si->item);
+ //
+ pos = (DC->cursory - r.y - SCROLLBAR_SIZE/2) * max / (r.h - SCROLLBAR_SIZE);
+ if (pos < 0) {
+ pos = 0;
+ }
+ else if (pos > max) {
+ pos = max;
+ }
+ listPtr->startPos = pos;
+ si->yStart = DC->cursory;
+ }
+
+ if (DC->realTime > si->nextScrollTime) {
+ // need to scroll which is done by simulating a click to the item
+ // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
+ // so it calls it directly
+ Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse);
+ si->nextScrollTime = DC->realTime + si->adjustValue;
+ }
+
+ if (DC->realTime > si->nextAdjustTime) {
+ si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+ if (si->adjustValue > SCROLL_TIME_FLOOR) {
+ si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
+ }
+ }
+}
+
+static void Scroll_Slider_ThumbFunc(void *p) {
+ float x, value, cursorx;
+ scrollInfo_t *si = (scrollInfo_t*)p;
+ editFieldDef_t *editDef = si->item->typeData;
+
+ if (si->item->text) {
+ x = si->item->textRect.x + si->item->textRect.w + 8;
+ } else {
+ x = si->item->window.rect.x;
+ }
+
+ cursorx = DC->cursorx;
+
+ if (cursorx < x) {
+ cursorx = x;
+ } else if (cursorx > x + SLIDER_WIDTH) {
+ cursorx = x + SLIDER_WIDTH;
+ }
+ value = cursorx - x;
+ value /= SLIDER_WIDTH;
+ value *= (editDef->maxVal - editDef->minVal);
+ value += editDef->minVal;
+ DC->setCVar(si->item->cvar, va("%f", value));
+}
+
+void Item_StartCapture(itemDef_t *item, int key) {
+ int flags;
+ switch (item->type) {
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_SAYFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+
+ case ITEM_TYPE_LISTBOX:
+ {
+ flags = Item_ListBox_OverLB(item, DC->cursorx, DC->cursory);
+ if (flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW)) {
+ scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START;
+ scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+ scrollInfo.adjustValue = SCROLL_TIME_START;
+ scrollInfo.scrollKey = key;
+ scrollInfo.scrollDir = (flags & WINDOW_LB_LEFTARROW) ? qtrue : qfalse;
+ scrollInfo.item = item;
+ captureData = &scrollInfo;
+ captureFunc = &Scroll_ListBox_AutoFunc;
+ itemCapture = item;
+ } else if (flags & WINDOW_LB_THUMB) {
+ scrollInfo.scrollKey = key;
+ scrollInfo.item = item;
+ scrollInfo.xStart = DC->cursorx;
+ scrollInfo.yStart = DC->cursory;
+ captureData = &scrollInfo;
+ captureFunc = &Scroll_ListBox_ThumbFunc;
+ itemCapture = item;
+ }
+ break;
+ }
+ case ITEM_TYPE_SLIDER:
+ {
+ flags = Item_Slider_OverSlider(item, DC->cursorx, DC->cursory);
+ if (flags & WINDOW_LB_THUMB) {
+ scrollInfo.scrollKey = key;
+ scrollInfo.item = item;
+ scrollInfo.xStart = DC->cursorx;
+ scrollInfo.yStart = DC->cursory;
+ captureData = &scrollInfo;
+ captureFunc = &Scroll_Slider_ThumbFunc;
+ itemCapture = item;
+ }
+ break;
+ }
+ }
+}
+
+void Item_StopCapture(itemDef_t *item) {
+
+}
+
+qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) {
+ float x, value, width, work;
+
+ if (item->window.flags & WINDOW_HASFOCUS && item->cvar && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) {
+ if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) {
+ editFieldDef_t *editDef = item->typeData;
+ if (editDef) {
+ rectDef_t testRect;
+ width = SLIDER_WIDTH;
+ if (item->text) {
+ x = item->textRect.x + item->textRect.w + 8;
+ } else {
+ x = item->window.rect.x;
+ }
+
+ testRect = item->window.rect;
+ testRect.x = x;
+ value = (float)SLIDER_THUMB_WIDTH / 2;
+ testRect.x -= value;
+ testRect.w = (SLIDER_WIDTH + (float)SLIDER_THUMB_WIDTH / 2);
+ if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory)) {
+ work = DC->cursorx - x;
+ value = work / width;
+ value *= (editDef->maxVal - editDef->minVal);
+ // vm fuckage
+ // value = (((float)(DC->cursorx - x)/ SLIDER_WIDTH) * (editDef->maxVal - editDef->minVal));
+ value += editDef->minVal;
+ DC->setCVar(item->cvar, va("%f", value));
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+
+qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) {
+
+ if (itemCapture) {
+ Item_StopCapture(itemCapture);
+ itemCapture = NULL;
+ captureFunc = voidFunction;
+ captureData = NULL;
+ } else {
+ // bk001206 - parentheses
+ if ( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) {
+ Item_StartCapture(item, key);
+ }
+ }
+
+ if (!down) {
+ return qfalse;
+ }
+
+ switch (item->type) {
+ case ITEM_TYPE_BUTTON:
+ return qfalse;
+ break;
+ case ITEM_TYPE_RADIOBUTTON:
+ return qfalse;
+ break;
+ case ITEM_TYPE_CHECKBOX:
+ return qfalse;
+ break;
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_SAYFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+ //return Item_TextField_HandleKey(item, key);
+ return qfalse;
+ break;
+ case ITEM_TYPE_COMBO:
+ return qfalse;
+ break;
+ case ITEM_TYPE_LISTBOX:
+ return Item_ListBox_HandleKey(item, key, down, qfalse);
+ break;
+ case ITEM_TYPE_YESNO:
+ return Item_YesNo_HandleKey(item, key);
+ break;
+ case ITEM_TYPE_MULTI:
+ return Item_Multi_HandleKey(item, key);
+ break;
+ case ITEM_TYPE_OWNERDRAW:
+ return Item_OwnerDraw_HandleKey(item, key);
+ break;
+ case ITEM_TYPE_BIND:
+ return Item_Bind_HandleKey(item, key, down);
+ break;
+ case ITEM_TYPE_SLIDER:
+ return Item_Slider_HandleKey(item, key, down);
+ break;
+ //case ITEM_TYPE_IMAGE:
+ // Item_Image_Paint(item);
+ // break;
+ default:
+ return qfalse;
+ break;
+ }
+
+ //return qfalse;
+}
+
+void Item_Action(itemDef_t *item) {
+ if (item) {
+ Item_RunScript(item, item->action);
+ }
+}
+
+itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu) {
+ qboolean wrapped = qfalse;
+ int oldCursor = menu->cursorItem;
+
+ if (menu->cursorItem < 0) {
+ menu->cursorItem = menu->itemCount-1;
+ wrapped = qtrue;
+ }
+
+ while (menu->cursorItem > -1) {
+
+ menu->cursorItem--;
+ if (menu->cursorItem < 0 && !wrapped) {
+ wrapped = qtrue;
+ menu->cursorItem = menu->itemCount -1;
+ }
+
+ if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) {
+ Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1);
+ return menu->items[menu->cursorItem];
+ }
+ }
+ menu->cursorItem = oldCursor;
+ return NULL;
+
+}
+
+itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu) {
+
+ qboolean wrapped = qfalse;
+ int oldCursor = menu->cursorItem;
+
+
+ if (menu->cursorItem == -1) {
+ menu->cursorItem = 0;
+ wrapped = qtrue;
+ }
+
+ while (menu->cursorItem < menu->itemCount) {
+
+ menu->cursorItem++;
+ if (menu->cursorItem >= menu->itemCount && !wrapped) {
+ wrapped = qtrue;
+ menu->cursorItem = 0;
+ }
+ if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) {
+ Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1);
+ return menu->items[menu->cursorItem];
+ }
+
+ }
+
+ menu->cursorItem = oldCursor;
+ return NULL;
+}
+
+static void Window_CloseCinematic(windowDef_t *window) {
+ if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0) {
+ DC->stopCinematic(window->cinematic);
+ window->cinematic = -1;
+ }
+}
+
+static void Menu_CloseCinematics(menuDef_t *menu) {
+ if (menu) {
+ int i;
+ Window_CloseCinematic(&menu->window);
+ for (i = 0; i < menu->itemCount; i++) {
+ Window_CloseCinematic(&menu->items[i]->window);
+ if (menu->items[i]->type == ITEM_TYPE_OWNERDRAW) {
+ DC->stopCinematic(0-menu->items[i]->window.ownerDraw);
+ }
+ }
+ }
+}
+
+static void Display_CloseCinematics( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ Menu_CloseCinematics(&Menus[i]);
+ }
+}
+
+void Menus_Activate(menuDef_t *menu) {
+ menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ if (menu->onOpen) {
+ itemDef_t item;
+ item.parent = menu;
+ Item_RunScript(&item, menu->onOpen);
+ }
+
+ if (menu->soundName && *menu->soundName) {
+// DC->stopBackgroundTrack(); // you don't want to do this since it will reset s_rawend
+ DC->startBackgroundTrack(menu->soundName, menu->soundName);
+ }
+
+ Display_CloseCinematics();
+
+}
+
+int Display_VisibleMenuCount( void ) {
+ int i, count;
+ count = 0;
+ for (i = 0; i < menuCount; i++) {
+ if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) {
+ if (menu) {
+ int i;
+ // basically the behaviour we are looking for is if there are windows in the stack.. see if
+ // the cursor is within any of them.. if not close them otherwise activate them and pass the
+ // key on.. force a mouse move to activate focus and script stuff
+ if (down && menu->window.flags & WINDOW_OOB_CLICK) {
+ Menu_RunCloseScript(menu);
+ menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ }
+
+ for (i = 0; i < menuCount; i++) {
+ if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) {
+ Menu_RunCloseScript(menu);
+ menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ Menus_Activate(&Menus[i]);
+ Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory);
+ Menu_HandleKey(&Menus[i], key, down);
+ }
+ }
+
+ if (Display_VisibleMenuCount() == 0) {
+ if (DC->Pause) {
+ DC->Pause(qfalse);
+ }
+ }
+ Display_CloseCinematics();
+ }
+}
+
+static rectDef_t *Item_CorrectedTextRect(itemDef_t *item) {
+ static rectDef_t rect;
+ memset(&rect, 0, sizeof(rectDef_t));
+ if (item) {
+ rect = item->textRect;
+ if (rect.w) {
+ rect.y -= rect.h;
+ }
+ }
+ return &rect;
+}
+
+void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) {
+ int i;
+ itemDef_t *item = NULL;
+ qboolean inHandler = qfalse;
+
+ if (inHandler) {
+ return;
+ }
+
+ inHandler = qtrue;
+ if (g_waitingForKey && down) {
+ Item_Bind_HandleKey(g_bindItem, key, down);
+ inHandler = qfalse;
+ return;
+ }
+
+ if (g_editingField && down) {
+ if (!Item_TextField_HandleKey(g_editItem, key)) {
+ g_editingField = qfalse;
+ Item_RunScript(g_editItem, g_editItem->onTextEntry);
+ g_editItem = NULL;
+ inHandler = qfalse;
+ return;
+ }
+ }
+
+ if (menu == NULL) {
+ inHandler = qfalse;
+ return;
+ }
+
+ // see if the mouse is within the window bounds and if so is this a mouse click
+ if (down && !(menu->window.flags & WINDOW_POPUP) && !Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory)) {
+ static qboolean inHandleKey = qfalse;
+ // bk001206 - parentheses
+ if (!inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) {
+ inHandleKey = qtrue;
+ Menus_HandleOOBClick(menu, key, down);
+ inHandleKey = qfalse;
+ inHandler = qfalse;
+ return;
+ }
+ }
+
+ // get the item with focus
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->window.flags & WINDOW_HASFOCUS) {
+ item = menu->items[i];
+ }
+ }
+
+ if (item != NULL) {
+ if (Item_HandleKey(item, key, down)) {
+ Item_Action(item);
+ inHandler = qfalse;
+ return;
+ }
+ }
+
+ if (!down) {
+ inHandler = qfalse;
+ return;
+ }
+
+ // default handling
+ switch ( key ) {
+
+ case K_F11:
+ if (DC->getCVarValue("developer")) {
+ debugMode ^= 1;
+ }
+ break;
+
+ case K_F12:
+ if (DC->getCVarValue("developer")) {
+ DC->executeText(EXEC_APPEND, "screenshot\n");
+ }
+ break;
+ case K_KP_UPARROW:
+ case K_UPARROW:
+ Menu_SetPrevCursorItem(menu);
+ break;
+
+ case K_ESCAPE:
+ if (!g_waitingForKey && menu->onESC) {
+ itemDef_t it;
+ it.parent = menu;
+ Item_RunScript(&it, menu->onESC);
+ }
+ break;
+ case K_TAB:
+ case K_KP_DOWNARROW:
+ case K_DOWNARROW:
+ Menu_SetNextCursorItem(menu);
+ break;
+
+ case K_MOUSE1:
+ case K_MOUSE2:
+ if (item) {
+ if (item->type == ITEM_TYPE_TEXT) {
+ if (Rect_ContainsPoint(Item_CorrectedTextRect(item), DC->cursorx, DC->cursory)) {
+ Item_Action(item);
+ }
+ } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) {
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) {
+ item->cursorPos = 0;
+ g_editingField = qtrue;
+ g_editItem = item;
+ DC->setOverstrikeMode(qtrue);
+ }
+ } else if (item->type == ITEM_TYPE_SAYFIELD) {
+ // do nothing
+ } else {
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) {
+ Item_Action(item);
+ }
+ }
+ }
+ break;
+
+ case K_JOY1:
+ case K_JOY2:
+ case K_JOY3:
+ case K_JOY4:
+ case K_AUX1:
+ case K_AUX2:
+ case K_AUX3:
+ case K_AUX4:
+ case K_AUX5:
+ case K_AUX6:
+ case K_AUX7:
+ case K_AUX8:
+ case K_AUX9:
+ case K_AUX10:
+ case K_AUX11:
+ case K_AUX12:
+ case K_AUX13:
+ case K_AUX14:
+ case K_AUX15:
+ case K_AUX16:
+ break;
+ case K_KP_ENTER:
+ case K_ENTER:
+ if (item) {
+ if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD || item->type == ITEM_TYPE_NUMERICFIELD) {
+ item->cursorPos = 0;
+ g_editingField = qtrue;
+ g_editItem = item;
+ DC->setOverstrikeMode(qtrue);
+ } else {
+ Item_Action(item);
+ }
+ }
+ break;
+ }
+ inHandler = qfalse;
+}
+
+void ToWindowCoords(float *x, float *y, windowDef_t *window) {
+ if (window->border != 0) {
+ *x += window->borderSize;
+ *y += window->borderSize;
+ }
+ *x += window->rect.x;
+ *y += window->rect.y;
+}
+
+void Rect_ToWindowCoords(rectDef_t *rect, windowDef_t *window) {
+ ToWindowCoords(&rect->x, &rect->y, window);
+}
+
+void Item_SetTextExtents(itemDef_t *item, int *width, int *height, const char *text) {
+ const char *textPtr = (text) ? text : item->text;
+
+ if (textPtr == NULL ) {
+ return;
+ }
+
+ *width = item->textRect.w;
+ *height = item->textRect.h;
+
+ // keeps us from computing the widths and heights more than once
+ if (*width == 0 || (item->type == ITEM_TYPE_OWNERDRAW && item->textalignment == ITEM_ALIGN_CENTER)) {
+ int originalWidth = DC->textWidth(item->text, item->textscale, 0);
+
+ if (item->type == ITEM_TYPE_OWNERDRAW && (item->textalignment == ITEM_ALIGN_CENTER || item->textalignment == ITEM_ALIGN_RIGHT)) {
+ originalWidth += DC->ownerDrawWidth(item->window.ownerDraw, item->textscale);
+ } else if (item->type == ITEM_TYPE_EDITFIELD && item->textalignment == ITEM_ALIGN_CENTER && item->cvar) {
+ char buff[256];
+ DC->getCVarString(item->cvar, buff, 256);
+ originalWidth += DC->textWidth(buff, item->textscale, 0);
+ }
+
+ *width = DC->textWidth(textPtr, item->textscale, 0);
+ *height = DC->textHeight(textPtr, item->textscale, 0);
+ item->textRect.w = *width;
+ item->textRect.h = *height;
+ item->textRect.x = item->textalignx;
+ item->textRect.y = item->textaligny;
+ if (item->textalignment == ITEM_ALIGN_RIGHT) {
+ item->textRect.x = item->textalignx - originalWidth;
+ } else if (item->textalignment == ITEM_ALIGN_CENTER) {
+ item->textRect.x = item->textalignx - originalWidth / 2;
+ }
+
+ ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window);
+ }
+}
+
+void Item_TextColor(itemDef_t *item, vec4_t *newColor) {
+ vec4_t lowLight;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount);
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) {
+ lowLight[0] = 0.8 * item->window.foreColor[0];
+ lowLight[1] = 0.8 * item->window.foreColor[1];
+ lowLight[2] = 0.8 * item->window.foreColor[2];
+ lowLight[3] = 0.8 * item->window.foreColor[3];
+ LerpColor(item->window.foreColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));
+ } else {
+ memcpy(newColor, &item->window.foreColor, sizeof(vec4_t));
+ // items can be enabled and disabled based on cvars
+ }
+
+ if (item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest) {
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ memcpy(newColor, &parent->disableColor, sizeof(vec4_t));
+ }
+ }
+}
+
+int Item_Text_AutoWrapped_Lines( itemDef_t *item )
+{
+ char text[ 1024 ];
+ const char *p, *textPtr, *newLinePtr;
+ char buff[ 1024 ];
+ int len, textWidth, newLine;
+ int lines = 0;
+
+ textWidth = 0;
+ newLinePtr = NULL;
+
+ if( item->text == NULL )
+ {
+ if( item->cvar == NULL )
+ return 0;
+ else
+ {
+ DC->getCVarString( item->cvar, text, sizeof( text ) );
+ textPtr = text;
+ }
+ }
+ else
+ textPtr = item->text;
+
+ if( *textPtr == '\0' )
+ return 0;
+
+ len = 0;
+ buff[ 0 ] = '\0';
+ newLine = 0;
+ p = textPtr;
+
+ while( p )
+ {
+ textWidth = DC->textWidth( buff, item->textscale, 0 );
+
+ if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' )
+ {
+ newLine = len;
+ newLinePtr = p + 1;
+ }
+
+ //TA: forceably split lines that are too long (where normal splitage has failed)
+ if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' )
+ {
+ newLine = len;
+ newLinePtr = p;
+ }
+
+ if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' )
+ {
+ if( len )
+ buff[ newLine ] = '\0';
+
+ if( !( *p == '\n' && !*( p + 1 ) ) )
+ lines++;
+
+ if( *p == '\0' )
+ break;
+
+ //
+ p = newLinePtr;
+ len = 0;
+ newLine = 0;
+
+ continue;
+ }
+
+ buff[ len++ ] = *p++;
+ buff[ len ] = '\0';
+ }
+
+ return lines;
+}
+
+#define MAX_AUTOWRAP_CACHE 16
+#define MAX_AUTOWRAP_LINES 32
+#define MAX_AUTOWRAP_TEXT 512
+
+typedef struct
+{
+ //this is used purely for checking for cache hits
+ char text[ MAX_AUTOWRAP_TEXT * MAX_AUTOWRAP_LINES ];
+ rectDef_t rect;
+ int textWidth, textHeight;
+ char lines[ MAX_AUTOWRAP_LINES ][ MAX_AUTOWRAP_TEXT ];
+ int lineOffsets[ MAX_AUTOWRAP_LINES ][ 2 ];
+ int numLines;
+} autoWrapCache_t;
+
+static int cacheIndex = 0;
+static autoWrapCache_t awc[ MAX_AUTOWRAP_CACHE ];
+
+static int checkCache( const char *text, rectDef_t *rect, int width, int height )
+{
+ int i;
+
+ for( i = 0; i < MAX_AUTOWRAP_CACHE; i++ )
+ {
+ if( Q_stricmp( text, awc[ i ].text ) )
+ continue;
+
+ if( rect->x != awc[ i ].rect.x ||
+ rect->y != awc[ i ].rect.y ||
+ rect->w != awc[ i ].rect.w ||
+ rect->h != awc[ i ].rect.h )
+ continue;
+
+ if( awc[ i ].textWidth != width || awc[ i ].textHeight != height )
+ continue;
+
+ //this is a match
+ return i;
+ }
+
+ //no match - autowrap isn't cached
+ return -1;
+}
+
+void Item_Text_AutoWrapped_Paint( itemDef_t *item )
+{
+ char text[ 1024 ];
+ const char *p, *textPtr, *newLinePtr;
+ char buff[ 1024 ];
+ char lastCMod[ 2 ] = { 0, 0 };
+ qboolean forwardColor = qfalse;
+ int width, height, len, textWidth, newLine, newLineWidth;
+ int skipLines, totalLines, lineNum = 0;
+ float y, totalY, diffY;
+ vec4_t color;
+ int cache, i;
+
+ textWidth = 0;
+ newLinePtr = NULL;
+
+ if( item->text == NULL )
+ {
+ if( item->cvar == NULL )
+ return;
+ else
+ {
+ DC->getCVarString( item->cvar, text, sizeof( text ) );
+ textPtr = text;
+ }
+ }
+ else
+ textPtr = item->text;
+
+ if( *textPtr == '\0' )
+ return;
+
+ Item_TextColor( item, &color );
+ Item_SetTextExtents( item, &width, &height, textPtr );
+
+ //check if this block is cached
+ cache = checkCache( textPtr, &item->window.rect, width, height );
+ if( cache >= 0 )
+ {
+ lineNum = awc[ cache ].numLines;
+
+ for( i = 0; i < lineNum; i++ )
+ {
+ item->textRect.x = awc[ cache ].lineOffsets[ i ][ 0 ];
+ item->textRect.y = awc[ cache ].lineOffsets[ i ][ 1 ];
+
+ DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color,
+ awc[ cache ].lines[ i ], 0, 0, item->textStyle );
+ }
+ }
+ else
+ {
+ y = item->textaligny;
+ len = 0;
+ buff[ 0 ] = '\0';
+ newLine = 0;
+ newLineWidth = 0;
+ p = textPtr;
+
+ totalLines = Item_Text_AutoWrapped_Lines( item );
+
+ totalY = totalLines * ( height + 5 );
+ diffY = totalY - item->window.rect.h;
+
+ if( diffY > 0.0f )
+ skipLines = (int)( diffY / ( (float)height + 5.0f ) );
+ else
+ skipLines = 0;
+
+ //set up a cache entry
+ strcpy( awc[ cacheIndex ].text, textPtr );
+ awc[ cacheIndex ].rect.x = item->window.rect.x;
+ awc[ cacheIndex ].rect.y = item->window.rect.y;
+ awc[ cacheIndex ].rect.w = item->window.rect.w;
+ awc[ cacheIndex ].rect.h = item->window.rect.h;
+ awc[ cacheIndex ].textWidth = width;
+ awc[ cacheIndex ].textHeight = height;
+
+ while( p )
+ {
+ textWidth = DC->textWidth( buff, item->textscale, 0 );
+
+ if( *p == '^' )
+ {
+ lastCMod[ 0 ] = p[ 0 ];
+ lastCMod[ 1 ] = p[ 1 ];
+ }
+
+ if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' )
+ {
+ newLine = len;
+ newLinePtr = p+1;
+ newLineWidth = textWidth;
+
+ if( *p == '\n' ) //don't forward colours past deilberate \n's
+ lastCMod[ 0 ] = lastCMod[ 1 ] = 0;
+ else
+ forwardColor = qtrue;
+ }
+
+ //TA: forceably split lines that are too long (where normal splitage has failed)
+ if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' )
+ {
+ newLine = len;
+ newLinePtr = p;
+ newLineWidth = textWidth;
+
+ forwardColor = qtrue;
+ }
+
+ if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' )
+ {
+ if( len )
+ {
+ if( item->textalignment == ITEM_ALIGN_LEFT )
+ item->textRect.x = item->textalignx;
+ else if( item->textalignment == ITEM_ALIGN_RIGHT )
+ item->textRect.x = item->textalignx - newLineWidth;
+ else if( item->textalignment == ITEM_ALIGN_CENTER )
+ item->textRect.x = item->textalignx - newLineWidth / 2;
+
+ item->textRect.y = y;
+ ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window );
+ //
+ buff[ newLine ] = '\0';
+
+ if( !skipLines )
+ {
+ DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, buff, 0, 0, item->textStyle );
+
+ strcpy( awc[ cacheIndex ].lines[ lineNum ], buff );
+ awc[ cacheIndex ].lineOffsets[ lineNum ][ 0 ] = item->textRect.x;
+ awc[ cacheIndex ].lineOffsets[ lineNum ][ 1 ] = item->textRect.y;
+
+ lineNum++;
+ }
+ }
+ if( *p == '\0' )
+ break;
+
+ //
+ if( !skipLines )
+ y += height + 5;
+
+ if( skipLines )
+ skipLines--;
+
+ p = newLinePtr;
+ len = 0;
+ newLine = 0;
+ newLineWidth = 0;
+
+ if( forwardColor && lastCMod[ 0 ] != 0 )
+ {
+ buff[ len++ ] = lastCMod[ 0 ];
+ buff[ len++ ] = lastCMod[ 1 ];
+ buff[ len ] = '\0';
+
+ forwardColor = qfalse;
+ }
+
+ continue;
+ }
+
+ buff[ len++ ] = *p++;
+ buff[ len ] = '\0';
+ }
+
+ //mark the end of the lines list
+ awc[ cacheIndex ].numLines = lineNum;
+
+ //increment cacheIndex
+ cacheIndex = ( cacheIndex + 1 ) % MAX_AUTOWRAP_CACHE;
+ }
+}
+
+void Item_Text_Wrapped_Paint(itemDef_t *item) {
+ char text[1024];
+ const char *p, *start, *textPtr;
+ char buff[1024];
+ int width, height;
+ float x, y;
+ vec4_t color;
+
+ // now paint the text and/or any optional images
+ // default to left
+
+ if (item->text == NULL) {
+ if (item->cvar == NULL) {
+ return;
+ }
+ else {
+ DC->getCVarString(item->cvar, text, sizeof(text));
+ textPtr = text;
+ }
+ }
+ else {
+ textPtr = item->text;
+ }
+ if (*textPtr == '\0') {
+ return;
+ }
+
+ Item_TextColor(item, &color);
+ Item_SetTextExtents(item, &width, &height, textPtr);
+
+ x = item->textRect.x;
+ y = item->textRect.y;
+ start = textPtr;
+ p = strchr(textPtr, '\r');
+ while (p && *p) {
+ strncpy(buff, start, p-start+1);
+ buff[p-start] = '\0';
+ DC->drawText(x, y, item->textscale, color, buff, 0, 0, item->textStyle);
+ y += height + 5;
+ start += p - start + 1;
+ p = strchr(p+1, '\r');
+ }
+ DC->drawText(x, y, item->textscale, color, start, 0, 0, item->textStyle);
+}
+
+void Item_Text_Paint(itemDef_t *item) {
+ char text[1024];
+ const char *textPtr;
+ int height, width;
+ vec4_t color;
+
+ if (item->window.flags & WINDOW_WRAPPED) {
+ Item_Text_Wrapped_Paint(item);
+ return;
+ }
+ if (item->window.flags & WINDOW_AUTOWRAPPED) {
+ Item_Text_AutoWrapped_Paint(item);
+ return;
+ }
+
+ if (item->text == NULL) {
+ if (item->cvar == NULL) {
+ return;
+ }
+ else {
+ DC->getCVarString(item->cvar, text, sizeof(text));
+ textPtr = text;
+ }
+ }
+ else {
+ textPtr = item->text;
+ }
+
+ // this needs to go here as it sets extents for cvar types as well
+ Item_SetTextExtents(item, &width, &height, textPtr);
+
+ if (*textPtr == '\0') {
+ return;
+ }
+
+
+ Item_TextColor(item, &color);
+
+ //FIXME: this is a fucking mess
+/*
+ adjust = 0;
+ if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) {
+ adjust = 0.5;
+ }
+
+ if (item->textStyle == ITEM_TEXTSTYLE_SHADOWED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) {
+ Fade(&item->window.flags, &DC->Assets.shadowColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse);
+ DC->drawText(item->textRect.x + DC->Assets.shadowX, item->textRect.y + DC->Assets.shadowY, item->textscale, DC->Assets.shadowColor, textPtr, adjust);
+ }
+*/
+
+
+// if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) {
+// Fade(&item->window.flags, &item->window.outlineColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse);
+// /*
+// Text_Paint(item->textRect.x-1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x+1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x-1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x+1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x-1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x+1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust);
+// */
+// DC->drawText(item->textRect.x - 1, item->textRect.y + 1, item->textscale * 1.02, item->window.outlineColor, textPtr, adjust);
+// }
+
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle);
+}
+
+
+
+//float trap_Cvar_VariableValue( const char *var_name );
+//void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+
+void Item_TextField_Paint(itemDef_t *item) {
+ char buff[1024];
+ vec4_t newColor;
+ int offset;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData;
+
+ Item_Text_Paint(item);
+
+ buff[0] = '\0';
+
+ if (item->cvar) {
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ }
+
+ parent = (menuDef_t*)item->parent;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ offset = (item->text && *item->text) ? 8 : 0;
+ if (item->window.flags & WINDOW_HASFOCUS && g_editingField) {
+ char cursor = DC->getOverstrikeMode() ? '_' : '|';
+ DC->drawTextWithCursor(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, item->cursorPos - editPtr->paintOffset , cursor, editPtr->maxPaintChars, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, 0, editPtr->maxPaintChars, item->textStyle);
+ }
+
+}
+
+void Item_YesNo_Paint(itemDef_t *item) {
+ vec4_t newColor;
+ float value;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle);
+ }
+}
+
+void Item_Multi_Paint(itemDef_t *item) {
+ vec4_t newColor;
+ const char *text = "";
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ text = Item_Multi_Setting(item);
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle);
+ }
+}
+
+
+typedef struct {
+ char *command;
+ int id;
+ int defaultbind1;
+ int defaultbind2;
+ int bind1;
+ int bind2;
+} bind_t;
+
+typedef struct
+{
+ char* name;
+ float defaultvalue;
+ float value;
+} configcvar_t;
+
+
+static bind_t g_bindings[] =
+{
+ { "+scores", K_TAB, -1, -1, -1 },
+ { "+button2", K_ENTER, -1, -1, -1 },
+ { "+speed", K_SHIFT, -1, -1, -1 },
+ { "boost", 'x', -1, -1, -1 }, //TA: human sprinting
+ { "+forward", K_UPARROW, -1, -1, -1 },
+ { "+back", K_DOWNARROW, -1, -1, -1 },
+ { "+moveleft", ',', -1, -1, -1 },
+ { "+moveright", '.', -1, -1, -1 },
+ { "+moveup", K_SPACE, -1, -1, -1 },
+ { "+movedown", 'c', -1, -1, -1 },
+ { "+left", K_LEFTARROW, -1, -1, -1 },
+ { "+right", K_RIGHTARROW, -1, -1, -1 },
+ { "+strafe", K_ALT, -1, -1, -1 },
+ { "+lookup", K_PGDN, -1, -1, -1 },
+ { "+lookdown", K_DEL, -1, -1, -1 },
+ { "+mlook", '/', -1, -1, -1 },
+ { "centerview", K_END, -1, -1, -1 },
+ { "+zoom", -1, -1, -1, -1 },
+ { "weapon 1", '1', -1, -1, -1 },
+ { "weapon 2", '2', -1, -1, -1 },
+ { "weapon 3", '3', -1, -1, -1 },
+ { "weapon 4", '4', -1, -1, -1 },
+ { "weapon 5", '5', -1, -1, -1 },
+ { "weapon 6", '6', -1, -1, -1 },
+ { "weapon 7", '7', -1, -1, -1 },
+ { "weapon 8", '8', -1, -1, -1 },
+ { "weapon 9", '9', -1, -1, -1 },
+ { "weapon 10", '0', -1, -1, -1 },
+ { "weapon 11", -1, -1, -1, -1 },
+ { "weapon 12", -1, -1, -1, -1 },
+ { "weapon 13", -1, -1, -1, -1 },
+ { "+attack", K_MOUSE1, -1, -1, -1 },
+ { "+button5", K_MOUSE2, -1, -1, -1 }, //TA: secondary attack
+ { "reload", 'r', -1, -1, -1 }, //TA: reload
+ { "buy ammo", 'b', -1, -1, -1 }, //TA: buy ammo
+ { "itemact medkit", 'm', -1, -1, -1 }, //TA: use medkit
+ { "+button7", 'q', -1, -1, -1 }, //TA: buildable use
+ { "deconstruct", 'e', -1, -1, -1 }, //TA: buildable destroy
+ { "weapprev", '[', -1, -1, -1 },
+ { "weapnext", ']', -1, -1, -1 },
+ { "+button3", K_MOUSE3, -1, -1, -1 },
+ { "+button4", K_MOUSE4, -1, -1, -1 },
+ { "vote yes", K_F1, -1, -1, -1 },
+ { "vote no", K_F2, -1, -1, -1 },
+ { "teamvote yes", K_F3, -1, -1, -1 },
+ { "teamvote no", K_F4, -1, -1, -1 },
+ { "scoresUp", K_KP_PGUP, -1, -1, -1 },
+ { "scoresDown", K_KP_PGDN, -1, -1, -1 },
+ // bk001205 - this one below was: '-1'
+ { "messagemode", -1, -1, -1, -1 },
+ { "messagemode2", -1, -1, -1, -1 },
+ { "messagemode3", -1, -1, -1, -1 },
+ { "messagemode4", -1, -1, -1, -1 }
+};
+
+
+static const int g_bindCount = sizeof(g_bindings) / sizeof(bind_t);
+
+/*
+=================
+Controls_GetKeyAssignment
+=================
+*/
+static void Controls_GetKeyAssignment (char *command, int *twokeys)
+{
+ int count;
+ int j;
+ char b[256];
+
+ twokeys[0] = twokeys[1] = -1;
+ count = 0;
+
+ for ( j = 0; j < 256; j++ )
+ {
+ DC->getBindingBuf( j, b, 256 );
+ if ( *b == 0 ) {
+ continue;
+ }
+ if ( !Q_stricmp( b, command ) ) {
+ twokeys[count] = j;
+ count++;
+ if (count == 2) {
+ break;
+ }
+ }
+ }
+}
+
+/*
+=================
+Controls_GetConfig
+=================
+*/
+void Controls_GetConfig( void )
+{
+ int i;
+ int twokeys[ 2 ];
+
+ // iterate each command, get its numeric binding
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ Controls_GetKeyAssignment( g_bindings[ i ].command, twokeys );
+
+ g_bindings[ i ].bind1 = twokeys[ 0 ];
+ g_bindings[ i ].bind2 = twokeys[ 1 ];
+ }
+
+ //s_controls.invertmouse.curvalue = DC->getCVarValue( "m_pitch" ) < 0;
+ //s_controls.smoothmouse.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "m_filter" ) );
+ //s_controls.alwaysrun.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_run" ) );
+ //s_controls.autoswitch.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cg_autoswitch" ) );
+ //s_controls.sensitivity.curvalue = UI_ClampCvar( 2, 30, Controls_GetCvarValue( "sensitivity" ) );
+ //s_controls.joyenable.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "in_joystick" ) );
+ //s_controls.joythreshold.curvalue = UI_ClampCvar( 0.05, 0.75, Controls_GetCvarValue( "joy_threshold" ) );
+ //s_controls.freelook.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_freelook" ) );
+}
+
+/*
+=================
+Controls_SetConfig
+=================
+*/
+void Controls_SetConfig(qboolean restart)
+{
+ int i;
+
+ // iterate each command, get its numeric binding
+ for (i=0; i < g_bindCount; i++)
+ {
+
+ if (g_bindings[i].bind1 != -1)
+ {
+ DC->setBinding( g_bindings[i].bind1, g_bindings[i].command );
+
+ if (g_bindings[i].bind2 != -1)
+ DC->setBinding( g_bindings[i].bind2, g_bindings[i].command );
+ }
+ }
+
+ //if ( s_controls.invertmouse.curvalue )
+ // DC->setCVar("m_pitch", va("%f),-fabs( DC->getCVarValue( "m_pitch" ) ) );
+ //else
+ // trap_Cvar_SetValue( "m_pitch", fabs( trap_Cvar_VariableValue( "m_pitch" ) ) );
+
+ //trap_Cvar_SetValue( "m_filter", s_controls.smoothmouse.curvalue );
+ //trap_Cvar_SetValue( "cl_run", s_controls.alwaysrun.curvalue );
+ //trap_Cvar_SetValue( "cg_autoswitch", s_controls.autoswitch.curvalue );
+ //trap_Cvar_SetValue( "sensitivity", s_controls.sensitivity.curvalue );
+ //trap_Cvar_SetValue( "in_joystick", s_controls.joyenable.curvalue );
+ //trap_Cvar_SetValue( "joy_threshold", s_controls.joythreshold.curvalue );
+ //trap_Cvar_SetValue( "cl_freelook", s_controls.freelook.curvalue );
+ DC->executeText(EXEC_APPEND, "in_restart\n");
+ //trap_Cmd_ExecuteText( EXEC_APPEND, "in_restart\n" );
+}
+
+/*
+=================
+Controls_SetDefaults
+=================
+*/
+void Controls_SetDefaults( void )
+{
+ int i;
+
+ // iterate each command, set its default binding
+ for (i=0; i < g_bindCount; i++)
+ {
+ g_bindings[i].bind1 = g_bindings[i].defaultbind1;
+ g_bindings[i].bind2 = g_bindings[i].defaultbind2;
+ }
+
+ //s_controls.invertmouse.curvalue = Controls_GetCvarDefault( "m_pitch" ) < 0;
+ //s_controls.smoothmouse.curvalue = Controls_GetCvarDefault( "m_filter" );
+ //s_controls.alwaysrun.curvalue = Controls_GetCvarDefault( "cl_run" );
+ //s_controls.autoswitch.curvalue = Controls_GetCvarDefault( "cg_autoswitch" );
+ //s_controls.sensitivity.curvalue = Controls_GetCvarDefault( "sensitivity" );
+ //s_controls.joyenable.curvalue = Controls_GetCvarDefault( "in_joystick" );
+ //s_controls.joythreshold.curvalue = Controls_GetCvarDefault( "joy_threshold" );
+ //s_controls.freelook.curvalue = Controls_GetCvarDefault( "cl_freelook" );
+}
+
+int BindingIDFromName(const char *name) {
+ int i;
+ for (i=0; i < g_bindCount; i++)
+ {
+ if (Q_stricmp(name, g_bindings[i].command) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+char g_nameBind1[32];
+char g_nameBind2[32];
+
+void BindingFromName(const char *cvar) {
+ int i, b1, b2;
+
+ // iterate each command, set its default binding
+ for (i=0; i < g_bindCount; i++)
+ {
+ if (Q_stricmp(cvar, g_bindings[i].command) == 0) {
+ b1 = g_bindings[i].bind1;
+ if (b1 == -1) {
+ break;
+ }
+ DC->keynumToStringBuf( b1, g_nameBind1, 32 );
+ Q_strupr(g_nameBind1);
+
+ b2 = g_bindings[i].bind2;
+ if (b2 != -1)
+ {
+ DC->keynumToStringBuf( b2, g_nameBind2, 32 );
+ Q_strupr(g_nameBind2);
+ strcat( g_nameBind1, " or " );
+ strcat( g_nameBind1, g_nameBind2 );
+ }
+ return;
+ }
+ }
+ strcpy(g_nameBind1, "???");
+}
+
+void Item_Slider_Paint(itemDef_t *item) {
+ vec4_t newColor;
+ float x, y, value;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ y = item->window.rect.y;
+ if (item->text) {
+ Item_Text_Paint(item);
+ x = item->textRect.x + item->textRect.w + 8;
+ } else {
+ x = item->window.rect.x;
+ }
+ DC->setColor(newColor);
+ DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT, DC->Assets.sliderBar );
+
+ x = Item_Slider_ThumbPosition(item);
+ DC->drawHandlePic( x - (SLIDER_THUMB_WIDTH / 2), y - 2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb );
+
+}
+
+void Item_Bind_Paint(itemDef_t *item) {
+ vec4_t newColor, lowLight;
+ float value;
+ int maxChars = 0;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData;
+ if (editPtr) {
+ maxChars = editPtr->maxPaintChars;
+ }
+
+ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+ if (g_bindItem == item) {
+ lowLight[0] = 0.8f * 1.0f;
+ lowLight[1] = 0.8f * 0.0f;
+ lowLight[2] = 0.8f * 0.0f;
+ lowLight[3] = 0.8f * 1.0f;
+ } else {
+ lowLight[0] = 0.8f * parent->focusColor[0];
+ lowLight[1] = 0.8f * parent->focusColor[1];
+ lowLight[2] = 0.8f * parent->focusColor[2];
+ lowLight[3] = 0.8f * parent->focusColor[3];
+ }
+ /*LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ BindingFromName(item->cvar);
+ DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle);
+ }
+}
+
+qboolean Display_KeyBindPending( void ) {
+ return g_waitingForKey;
+}
+
+qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) {
+ int id;
+ int i;
+
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey)
+ {
+ if (down && (key == K_MOUSE1 || key == K_ENTER)) {
+ g_waitingForKey = qtrue;
+ g_bindItem = item;
+ }
+ return qtrue;
+ }
+ else
+ {
+ if (!g_waitingForKey || g_bindItem == NULL) {
+ return qtrue;
+ }
+
+ if (key & K_CHAR_FLAG) {
+ return qtrue;
+ }
+
+ switch (key)
+ {
+ case K_ESCAPE:
+ g_waitingForKey = qfalse;
+ return qtrue;
+
+ case K_BACKSPACE:
+ id = BindingIDFromName(item->cvar);
+ if (id != -1) {
+ g_bindings[id].bind1 = -1;
+ g_bindings[id].bind2 = -1;
+ }
+ Controls_SetConfig(qtrue);
+ g_waitingForKey = qfalse;
+ g_bindItem = NULL;
+ return qtrue;
+
+ case '`':
+ return qtrue;
+ }
+ }
+
+ if (key != -1)
+ {
+
+ for (i=0; i < g_bindCount; i++)
+ {
+
+ if (g_bindings[i].bind2 == key) {
+ g_bindings[i].bind2 = -1;
+ }
+
+ if (g_bindings[i].bind1 == key)
+ {
+ g_bindings[i].bind1 = g_bindings[i].bind2;
+ g_bindings[i].bind2 = -1;
+ }
+ }
+ }
+
+
+ id = BindingIDFromName(item->cvar);
+
+ if (id != -1) {
+ if (key == -1) {
+ if( g_bindings[id].bind1 != -1 ) {
+ DC->setBinding( g_bindings[id].bind1, "" );
+ g_bindings[id].bind1 = -1;
+ }
+ if( g_bindings[id].bind2 != -1 ) {
+ DC->setBinding( g_bindings[id].bind2, "" );
+ g_bindings[id].bind2 = -1;
+ }
+ }
+ else if (g_bindings[id].bind1 == -1) {
+ g_bindings[id].bind1 = key;
+ }
+ else if (g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1) {
+ g_bindings[id].bind2 = key;
+ }
+ else {
+ DC->setBinding( g_bindings[id].bind1, "" );
+ DC->setBinding( g_bindings[id].bind2, "" );
+ g_bindings[id].bind1 = key;
+ g_bindings[id].bind2 = -1;
+ }
+ }
+
+ Controls_SetConfig(qtrue);
+ g_waitingForKey = qfalse;
+
+ return qtrue;
+}
+
+
+
+void AdjustFrom640(float *x, float *y, float *w, float *h) {
+ //*x = *x * DC->scale + DC->bias;
+ *x *= DC->xscale;
+ *y *= DC->yscale;
+ *w *= DC->xscale;
+ *h *= DC->yscale;
+}
+
+void Item_Model_Paint(itemDef_t *item) {
+ float x, y, w, h;
+ refdef_t refdef;
+ refEntity_t ent;
+ vec3_t mins, maxs, origin;
+ vec3_t angles;
+ modelDef_t *modelPtr = (modelDef_t*)item->typeData;
+
+ if (modelPtr == NULL) {
+ return;
+ }
+
+ // setup the refdef
+ memset( &refdef, 0, sizeof( refdef ) );
+ refdef.rdflags = RDF_NOWORLDMODEL;
+ AxisClear( refdef.viewaxis );
+ x = item->window.rect.x+1;
+ y = item->window.rect.y+1;
+ w = item->window.rect.w-2;
+ h = item->window.rect.h-2;
+
+ AdjustFrom640( &x, &y, &w, &h );
+
+ refdef.x = x;
+ refdef.y = y;
+ refdef.width = w;
+ refdef.height = h;
+
+ DC->modelBounds( item->asset, mins, maxs );
+
+ origin[2] = -0.5 * ( mins[2] + maxs[2] );
+ origin[1] = 0.5 * ( mins[1] + maxs[1] );
+
+ // calculate distance so the model nearly fills the box
+ if (qtrue) {
+ float len = 0.5 * ( maxs[2] - mins[2] );
+ origin[0] = len / 0.268; // len / tan( fov/2 )
+ //origin[0] = len / tan(w/2);
+ } else {
+ origin[0] = item->textscale;
+ }
+ refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : w;
+ refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : h;
+
+ //refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
+ //xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
+ //refdef.fov_y = atan2( refdef.height, xx );
+ //refdef.fov_y *= ( 360 / M_PI );
+
+ DC->clearScene();
+
+ refdef.time = DC->realTime;
+
+ // add the model
+
+ memset( &ent, 0, sizeof(ent) );
+
+ //adjust = 5.0 * sin( (float)uis.realtime / 500 );
+ //adjust = 360 % (int)((float)uis.realtime / 1000);
+ //VectorSet( angles, 0, 0, 1 );
+
+ // use item storage to track
+ if (modelPtr->rotationSpeed) {
+ if (DC->realTime > item->window.nextTime) {
+ item->window.nextTime = DC->realTime + modelPtr->rotationSpeed;
+ modelPtr->angle = (int)(modelPtr->angle + 1) % 360;
+ }
+ }
+ VectorSet( angles, 0, modelPtr->angle, 0 );
+ AnglesToAxis( angles, ent.axis );
+
+ ent.hModel = item->asset;
+ VectorCopy( origin, ent.origin );
+ VectorCopy( origin, ent.lightingOrigin );
+ ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
+ VectorCopy( ent.origin, ent.oldorigin );
+
+ DC->addRefEntityToScene( &ent );
+ DC->renderScene( &refdef );
+
+}
+
+
+void Item_Image_Paint(itemDef_t *item) {
+ if (item == NULL) {
+ return;
+ }
+ DC->drawHandlePic(item->window.rect.x+1, item->window.rect.y+1, item->window.rect.w-2, item->window.rect.h-2, item->asset);
+}
+
+void Item_ListBox_Paint(itemDef_t *item) {
+ float x, y, size, thumb;
+ int i, count;
+ qhandle_t image;
+ qhandle_t optionalImage;
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+
+ // the listbox is horizontal or vertical and has a fixed size scroll bar going either direction
+ // elements are enumerated from the DC and either text or image handles are acquired from the DC as well
+ // textscale is used to size the text, textalignx and textaligny are used to size image elements
+ // there is no clipping available so only the last completely visible item is painted
+ count = DC->feederCount(item->special);
+ // default is vertical if horizontal flag is not here
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ // draw scrollbar in bottom of the window
+ // bar
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE - 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowLeft);
+ x += SCROLLBAR_SIZE - 1;
+ size = item->window.rect.w - (SCROLLBAR_SIZE * 2);
+ DC->drawHandlePic(x, y, size+1, SCROLLBAR_SIZE, DC->Assets.scrollBar);
+ x += size - 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowRight);
+ // thumb
+ thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item);
+ if (thumb > x - SCROLLBAR_SIZE - 1) {
+ thumb = x - SCROLLBAR_SIZE - 1;
+ }
+ DC->drawHandlePic(thumb, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb);
+ //
+ listPtr->endPos = listPtr->startPos;
+ size = item->window.rect.w - 2;
+ // items
+ // size contains max available space
+ if (listPtr->elementStyle == LISTBOX_IMAGE) {
+ // fit = 0;
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + 1;
+ for (i = listPtr->startPos; i < count; i++) {
+ // always draw at least one
+ // which may overdraw the box if it is too small for the element
+ image = DC->feederItemImage(item->special, i);
+ if (image) {
+ DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image);
+ }
+
+ if (i == item->cursorPos) {
+ DC->drawRect(x, y, listPtr->elementWidth-1, listPtr->elementHeight-1, item->window.borderSize, item->window.borderColor);
+ }
+
+ listPtr->endPos++;
+ size -= listPtr->elementWidth;
+ if (size < listPtr->elementWidth) {
+ listPtr->drawPadding = size; //listPtr->elementWidth - size;
+ break;
+ }
+ x += listPtr->elementWidth;
+ // fit++;
+ }
+ } else {
+ //
+ }
+ } else {
+ // draw scrollbar to right side of the window
+ x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE - 1;
+ y = item->window.rect.y + 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowUp);
+ y += SCROLLBAR_SIZE - 1;
+
+ listPtr->endPos = listPtr->startPos;
+ size = item->window.rect.h - (SCROLLBAR_SIZE * 2);
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, size+1, DC->Assets.scrollBar);
+ y += size - 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowDown);
+ // thumb
+ thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item);
+ if (thumb > y - SCROLLBAR_SIZE - 1) {
+ thumb = y - SCROLLBAR_SIZE - 1;
+ }
+ DC->drawHandlePic(x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb);
+
+ // adjust size for item painting
+ size = item->window.rect.h - 2;
+ if (listPtr->elementStyle == LISTBOX_IMAGE) {
+ // fit = 0;
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + 1;
+ for (i = listPtr->startPos; i < count; i++) {
+ // always draw at least one
+ // which may overdraw the box if it is too small for the element
+ image = DC->feederItemImage(item->special, i);
+ if (image) {
+ DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image);
+ }
+
+ if (i == item->cursorPos) {
+ DC->drawRect(x, y, listPtr->elementWidth - 1, listPtr->elementHeight - 1, item->window.borderSize, item->window.borderColor);
+ }
+
+ listPtr->endPos++;
+ size -= listPtr->elementWidth;
+ if (size < listPtr->elementHeight) {
+ listPtr->drawPadding = listPtr->elementHeight - size;
+ break;
+ }
+ y += listPtr->elementHeight;
+ // fit++;
+ }
+ } else {
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + 1;
+ for (i = listPtr->startPos; i < count; i++) {
+ const char *text;
+ // always draw at least one
+ // which may overdraw the box if it is too small for the element
+
+ if (listPtr->numColumns > 0) {
+ int j;
+ for (j = 0; j < listPtr->numColumns; j++) {
+ text = DC->feederItemText(item->special, i, j, &optionalImage);
+ if (optionalImage >= 0) {
+ DC->drawHandlePic(x + 4 + listPtr->columnInfo[j].pos, y - 1 + listPtr->elementHeight / 2, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage);
+ } else if (text) {
+ //TA:
+ int alignOffset = 0.0f, tw;
+
+ tw = DC->textWidth( text, item->textscale, 0 );
+
+ switch( listPtr->columnInfo[ j ].align )
+ {
+ case ITEM_ALIGN_LEFT:
+ alignOffset = 0.0f;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ alignOffset = listPtr->columnInfo[ j ].width - tw;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ alignOffset = ( listPtr->columnInfo[ j ].width / 2.0f ) - ( tw / 2.0f );
+ break;
+
+ default:
+ alignOffset = 0.0f;
+ }
+
+ DC->drawText( x + 4 + listPtr->columnInfo[j].pos + alignOffset, y + listPtr->elementHeight,
+ item->textscale, item->window.foreColor, text, 0,
+ listPtr->columnInfo[j].maxChars, item->textStyle );
+ }
+ }
+ } else {
+ text = DC->feederItemText(item->special, i, 0, &optionalImage);
+ if (optionalImage >= 0) {
+ //DC->drawHandlePic(x + 4 + listPtr->elementHeight, y, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage);
+ } else if (text) {
+ DC->drawText(x + 4, y + listPtr->elementHeight, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle);
+ }
+ }
+
+ if (i == item->cursorPos) {
+ DC->fillRect(x + 2, y + 2, item->window.rect.w - SCROLLBAR_SIZE - 4, listPtr->elementHeight, item->window.outlineColor);
+ }
+
+ listPtr->endPos++;
+ size -= listPtr->elementHeight;
+ if (size < listPtr->elementHeight) {
+ listPtr->drawPadding = listPtr->elementHeight - size;
+ break;
+ }
+ y += listPtr->elementHeight;
+ // fit++;
+ }
+ }
+ }
+
+ //TA: FIXME: hacky fix to off-by-one bug
+ listPtr->endPos--;
+}
+
+
+void Item_OwnerDraw_Paint(itemDef_t *item) {
+ menuDef_t *parent;
+
+ if (item == NULL) {
+ return;
+ }
+ parent = (menuDef_t*)item->parent;
+
+ if (DC->ownerDrawItem) {
+ vec4_t color, lowLight;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount);
+ memcpy(&color, &item->window.foreColor, sizeof(color));
+ if (item->numColors > 0 && DC->getValue) {
+ // if the value is within one of the ranges then set color to that, otherwise leave at default
+ int i;
+ float f = DC->getValue(item->window.ownerDraw);
+ for (i = 0; i < item->numColors; i++) {
+ if (f >= item->colorRanges[i].low && f <= item->colorRanges[i].high) {
+ memcpy(&color, &item->colorRanges[i].color, sizeof(color));
+ break;
+ }
+ }
+ }
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(color, &parent->focusColor, sizeof(vec4_t));
+ } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) {
+ lowLight[0] = 0.8 * item->window.foreColor[0];
+ lowLight[1] = 0.8 * item->window.foreColor[1];
+ lowLight[2] = 0.8 * item->window.foreColor[2];
+ lowLight[3] = 0.8 * item->window.foreColor[3];
+ LerpColor(item->window.foreColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));
+ }
+
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ memcpy(color, parent->disableColor, sizeof(vec4_t)); // bk001207 - FIXME: Com_Memcpy
+ }
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ if (item->text[0]) {
+ // +8 is an offset kludge to properly align owner draw items that have text combined with them
+ DC->ownerDrawItem(item->textRect.x + item->textRect.w + 8, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle );
+ } else {
+ DC->ownerDrawItem(item->textRect.x + item->textRect.w, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle );
+ }
+ } else {
+ DC->ownerDrawItem(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle );
+ }
+ }
+}
+
+
+void Item_Paint(itemDef_t *item) {
+ vec4_t red;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ red[0] = red[3] = 1;
+ red[1] = red[2] = 0;
+
+ if (item == NULL) {
+ return;
+ }
+
+ if (item->window.flags & WINDOW_ORBITING) {
+ if (DC->realTime > item->window.nextTime) {
+ float rx, ry, a, c, s, w, h;
+
+ item->window.nextTime = DC->realTime + item->window.offsetTime;
+ // translate
+ w = item->window.rectClient.w / 2;
+ h = item->window.rectClient.h / 2;
+ rx = item->window.rectClient.x + w - item->window.rectEffects.x;
+ ry = item->window.rectClient.y + h - item->window.rectEffects.y;
+ a = 3 * M_PI / 180;
+ c = cos(a);
+ s = sin(a);
+ item->window.rectClient.x = (rx * c - ry * s) + item->window.rectEffects.x - w;
+ item->window.rectClient.y = (rx * s + ry * c) + item->window.rectEffects.y - h;
+ Item_UpdatePosition(item);
+
+ }
+ }
+
+
+ if (item->window.flags & WINDOW_INTRANSITION) {
+ if (DC->realTime > item->window.nextTime) {
+ int done = 0;
+ item->window.nextTime = DC->realTime + item->window.offsetTime;
+ // transition the x,y
+ if (item->window.rectClient.x == item->window.rectEffects.x) {
+ done++;
+ } else {
+ if (item->window.rectClient.x < item->window.rectEffects.x) {
+ item->window.rectClient.x += item->window.rectEffects2.x;
+ if (item->window.rectClient.x > item->window.rectEffects.x) {
+ item->window.rectClient.x = item->window.rectEffects.x;
+ done++;
+ }
+ } else {
+ item->window.rectClient.x -= item->window.rectEffects2.x;
+ if (item->window.rectClient.x < item->window.rectEffects.x) {
+ item->window.rectClient.x = item->window.rectEffects.x;
+ done++;
+ }
+ }
+ }
+ if (item->window.rectClient.y == item->window.rectEffects.y) {
+ done++;
+ } else {
+ if (item->window.rectClient.y < item->window.rectEffects.y) {
+ item->window.rectClient.y += item->window.rectEffects2.y;
+ if (item->window.rectClient.y > item->window.rectEffects.y) {
+ item->window.rectClient.y = item->window.rectEffects.y;
+ done++;
+ }
+ } else {
+ item->window.rectClient.y -= item->window.rectEffects2.y;
+ if (item->window.rectClient.y < item->window.rectEffects.y) {
+ item->window.rectClient.y = item->window.rectEffects.y;
+ done++;
+ }
+ }
+ }
+ if (item->window.rectClient.w == item->window.rectEffects.w) {
+ done++;
+ } else {
+ if (item->window.rectClient.w < item->window.rectEffects.w) {
+ item->window.rectClient.w += item->window.rectEffects2.w;
+ if (item->window.rectClient.w > item->window.rectEffects.w) {
+ item->window.rectClient.w = item->window.rectEffects.w;
+ done++;
+ }
+ } else {
+ item->window.rectClient.w -= item->window.rectEffects2.w;
+ if (item->window.rectClient.w < item->window.rectEffects.w) {
+ item->window.rectClient.w = item->window.rectEffects.w;
+ done++;
+ }
+ }
+ }
+ if (item->window.rectClient.h == item->window.rectEffects.h) {
+ done++;
+ } else {
+ if (item->window.rectClient.h < item->window.rectEffects.h) {
+ item->window.rectClient.h += item->window.rectEffects2.h;
+ if (item->window.rectClient.h > item->window.rectEffects.h) {
+ item->window.rectClient.h = item->window.rectEffects.h;
+ done++;
+ }
+ } else {
+ item->window.rectClient.h -= item->window.rectEffects2.h;
+ if (item->window.rectClient.h < item->window.rectEffects.h) {
+ item->window.rectClient.h = item->window.rectEffects.h;
+ done++;
+ }
+ }
+ }
+
+ Item_UpdatePosition(item);
+
+ if (done == 4) {
+ item->window.flags &= ~WINDOW_INTRANSITION;
+ }
+
+ }
+ }
+
+ if (item->window.ownerDrawFlags && DC->ownerDrawVisible) {
+ if (!DC->ownerDrawVisible(item->window.ownerDrawFlags)) {
+ item->window.flags &= ~WINDOW_VISIBLE;
+ } else {
+ item->window.flags |= WINDOW_VISIBLE;
+ }
+ }
+
+ if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE)) {
+ if (!Item_EnableShowViaCvar(item, CVAR_SHOW)) {
+ return;
+ }
+ }
+
+ if (item->window.flags & WINDOW_TIMEDVISIBLE) {
+
+ }
+
+ if (!(item->window.flags & WINDOW_VISIBLE)) {
+ return;
+ }
+
+ // paint the rect first..
+ Window_Paint(&item->window, parent->fadeAmount , parent->fadeClamp, parent->fadeCycle);
+
+ if (debugMode) {
+ vec4_t color;
+ rectDef_t *r = Item_CorrectedTextRect(item);
+ color[1] = color[3] = 1;
+ color[0] = color[2] = 0;
+ DC->drawRect(r->x, r->y, r->w, r->h, 1, color);
+ }
+
+ //DC->drawRect(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, 1, red);
+
+ switch (item->type) {
+ case ITEM_TYPE_OWNERDRAW:
+ Item_OwnerDraw_Paint(item);
+ break;
+ case ITEM_TYPE_TEXT:
+ case ITEM_TYPE_BUTTON:
+ Item_Text_Paint(item);
+ break;
+ case ITEM_TYPE_RADIOBUTTON:
+ break;
+ case ITEM_TYPE_CHECKBOX:
+ break;
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_SAYFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+ Item_TextField_Paint(item);
+ break;
+ case ITEM_TYPE_COMBO:
+ break;
+ case ITEM_TYPE_LISTBOX:
+ Item_ListBox_Paint(item);
+ break;
+ //case ITEM_TYPE_IMAGE:
+ // Item_Image_Paint(item);
+ // break;
+ case ITEM_TYPE_MODEL:
+ Item_Model_Paint(item);
+ break;
+ case ITEM_TYPE_YESNO:
+ Item_YesNo_Paint(item);
+ break;
+ case ITEM_TYPE_MULTI:
+ Item_Multi_Paint(item);
+ break;
+ case ITEM_TYPE_BIND:
+ Item_Bind_Paint(item);
+ break;
+ case ITEM_TYPE_SLIDER:
+ Item_Slider_Paint(item);
+ break;
+ default:
+ break;
+ }
+
+}
+
+void Menu_Init(menuDef_t *menu) {
+ memset(menu, 0, sizeof(menuDef_t));
+ menu->cursorItem = -1;
+ menu->fadeAmount = DC->Assets.fadeAmount;
+ menu->fadeClamp = DC->Assets.fadeClamp;
+ menu->fadeCycle = DC->Assets.fadeCycle;
+ Window_Init(&menu->window);
+}
+
+itemDef_t *Menu_GetFocusedItem(menuDef_t *menu) {
+ int i;
+ if (menu) {
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->window.flags & WINDOW_HASFOCUS) {
+ return menu->items[i];
+ }
+ }
+ }
+ return NULL;
+}
+
+menuDef_t *Menu_GetFocused( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ if (Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE) {
+ return &Menus[i];
+ }
+ }
+ return NULL;
+}
+
+void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down) {
+ if (menu) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->special == feeder) {
+ Item_ListBox_HandleKey(menu->items[i], (down) ? K_DOWNARROW : K_UPARROW, qtrue, qtrue);
+ return;
+ }
+ }
+ }
+}
+
+
+
+void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name) {
+ if (menu == NULL) {
+ if (name == NULL) {
+ menu = Menu_GetFocused();
+ } else {
+ menu = Menus_FindByName(name);
+ }
+ }
+
+ if (menu) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->special == feeder) {
+ if (index == 0) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)menu->items[i]->typeData;
+ listPtr->cursorPos = 0;
+ listPtr->startPos = 0;
+ }
+ menu->items[i]->cursorPos = index;
+ DC->feederSelection(menu->items[i]->special, menu->items[i]->cursorPos);
+ return;
+ }
+ }
+ }
+}
+
+qboolean Menus_AnyFullScreenVisible( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen) {
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+menuDef_t *Menus_ActivateByName(const char *p) {
+ int i, j;
+ menuDef_t *m = NULL;
+ menuDef_t *focus = Menu_GetFocused();
+
+ for (i = 0; i < menuCount; i++) {
+ if (Q_stricmp(Menus[i].window.name, p) == 0) {
+ m = &Menus[i];
+ Menus_Activate(m);
+ Menu_HandleMouseMove( m, DC->cursorx, DC->cursory ); //TA: force the item under the cursor to focus
+
+ for( j = 0; j < m->itemCount; j++ ) //TA: reset selection in listboxes when opened
+ {
+ if( m->items[ j ]->type == ITEM_TYPE_LISTBOX )
+ {
+ listBoxDef_t *listPtr = (listBoxDef_t*)m->items[ j ]->typeData;
+ m->items[ j ]->cursorPos = 0;
+ listPtr->startPos = 0;
+ DC->feederSelection( m->items[ j ]->special, 0 );
+ }
+ }
+
+ if (openMenuCount < MAX_OPEN_MENUS && focus != NULL) {
+ menuStack[openMenuCount++] = focus;
+ }
+ } else {
+ Menus[i].window.flags &= ~WINDOW_HASFOCUS;
+ }
+ }
+ Display_CloseCinematics();
+ return m;
+}
+
+
+void Item_Init(itemDef_t *item) {
+ memset(item, 0, sizeof(itemDef_t));
+ item->textscale = 0.55f;
+ Window_Init(&item->window);
+}
+
+void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) {
+ int i, pass;
+ qboolean focusSet = qfalse;
+
+ itemDef_t *overItem;
+ if (menu == NULL) {
+ return;
+ }
+
+ if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) {
+ return;
+ }
+
+ if (itemCapture) {
+ //Item_MouseMove(itemCapture, x, y);
+ return;
+ }
+
+ if (g_waitingForKey || g_editingField) {
+ return;
+ }
+
+ // FIXME: this is the whole issue of focus vs. mouse over..
+ // need a better overall solution as i don't like going through everything twice
+ for (pass = 0; pass < 2; pass++) {
+ for (i = 0; i < menu->itemCount; i++) {
+ // turn off focus each item
+ // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
+
+ if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) {
+ continue;
+ }
+
+ // items can be enabled and disabled based on cvars
+ if (menu->items[i]->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_ENABLE)) {
+ continue;
+ }
+
+ if (menu->items[i]->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_SHOW)) {
+ continue;
+ }
+
+
+
+ if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) {
+ if (pass == 1) {
+ overItem = menu->items[i];
+ if (overItem->type == ITEM_TYPE_TEXT && overItem->text) {
+ if (!Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) {
+ continue;
+ }
+ }
+ // if we are over an item
+ if (IsVisible(overItem->window.flags)) {
+ // different one
+ Item_MouseEnter(overItem, x, y);
+ // Item_SetMouseOver(overItem, qtrue);
+
+ // if item is not a decoration see if it can take focus
+ if (!focusSet) {
+ focusSet = Item_SetFocus(overItem, x, y);
+ }
+ }
+ }
+ } else if (menu->items[i]->window.flags & WINDOW_MOUSEOVER) {
+ Item_MouseLeave(menu->items[i]);
+ Item_SetMouseOver(menu->items[i], qfalse);
+ }
+ }
+ }
+
+}
+
+void Menu_Paint(menuDef_t *menu, qboolean forcePaint) {
+ int i;
+
+ if (menu == NULL) {
+ return;
+ }
+
+ if (!(menu->window.flags & WINDOW_VISIBLE) && !forcePaint) {
+ return;
+ }
+
+ if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags)) {
+ return;
+ }
+
+ if (forcePaint) {
+ menu->window.flags |= WINDOW_FORCED;
+ }
+
+ // draw the background if necessary
+ if (menu->fullScreen) {
+ // implies a background shader
+ // FIXME: make sure we have a default shader if fullscreen is set with no background
+ DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background );
+ } else if (menu->window.background) {
+ // this allows a background shader without being full screen
+ //UI_DrawHandlePic(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, menu->backgroundShader);
+ }
+
+ // paint the background and or border
+ Window_Paint(&menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle );
+
+ for (i = 0; i < menu->itemCount; i++) {
+ Item_Paint(menu->items[i]);
+ }
+
+ if (debugMode) {
+ vec4_t color;
+ color[0] = color[2] = color[3] = 1;
+ color[1] = 0;
+ DC->drawRect(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color);
+ }
+}
+
+/*
+===============
+Item_ValidateTypeData
+===============
+*/
+void Item_ValidateTypeData(itemDef_t *item) {
+ if (item->typeData) {
+ return;
+ }
+
+ if (item->type == ITEM_TYPE_LISTBOX) {
+ item->typeData = UI_Alloc(sizeof(listBoxDef_t));
+ memset(item->typeData, 0, sizeof(listBoxDef_t));
+ } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD || item->type == ITEM_TYPE_NUMERICFIELD || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_BIND || item->type == ITEM_TYPE_SLIDER || item->type == ITEM_TYPE_TEXT) {
+ item->typeData = UI_Alloc(sizeof(editFieldDef_t));
+ memset(item->typeData, 0, sizeof(editFieldDef_t));
+ if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD) {
+ if (!((editFieldDef_t *) item->typeData)->maxPaintChars) {
+ ((editFieldDef_t *) item->typeData)->maxPaintChars = MAX_EDITFIELD;
+ }
+ }
+ } else if (item->type == ITEM_TYPE_MULTI) {
+ item->typeData = UI_Alloc(sizeof(multiDef_t));
+ } else if (item->type == ITEM_TYPE_MODEL) {
+ item->typeData = UI_Alloc(sizeof(modelDef_t));
+ }
+}
+
+/*
+===============
+Keyword Hash
+===============
+*/
+
+#define KEYWORDHASH_SIZE 512
+
+typedef struct keywordHash_s
+{
+ char *keyword;
+ qboolean (*func)(itemDef_t *item, int handle);
+ struct keywordHash_s *next;
+} keywordHash_t;
+
+int KeywordHash_Key(char *keyword) {
+ int register hash, i;
+
+ hash = 0;
+ for (i = 0; keyword[i] != '\0'; i++) {
+ if (keyword[i] >= 'A' && keyword[i] <= 'Z')
+ hash += (keyword[i] + ('a' - 'A')) * (119 + i);
+ else
+ hash += keyword[i] * (119 + i);
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (KEYWORDHASH_SIZE-1);
+ return hash;
+}
+
+void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key) {
+ int hash;
+
+ hash = KeywordHash_Key(key->keyword);
+/*
+ if (table[hash]) {
+ int collision = qtrue;
+ }
+*/
+ key->next = table[hash];
+ table[hash] = key;
+}
+
+keywordHash_t *KeywordHash_Find(keywordHash_t *table[], char *keyword)
+{
+ keywordHash_t *key;
+ int hash;
+
+ hash = KeywordHash_Key(keyword);
+ for (key = table[hash]; key; key = key->next) {
+ if (!Q_stricmp(key->keyword, keyword))
+ return key;
+ }
+ return NULL;
+}
+
+/*
+===============
+Item Keyword Parse functions
+===============
+*/
+
+// name <string>
+qboolean ItemParse_name( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->window.name)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// name <string>
+qboolean ItemParse_focusSound( itemDef_t *item, int handle ) {
+ const char *temp;
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->focusSound = DC->registerSound(temp, qfalse);
+ return qtrue;
+}
+
+
+// text <string>
+qboolean ItemParse_text( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->text)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// group <string>
+qboolean ItemParse_group( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->window.group)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// asset_model <string>
+qboolean ItemParse_asset_model( itemDef_t *item, int handle ) {
+ const char *temp;
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->asset = DC->registerModel(temp);
+ modelPtr->angle = rand() % 360;
+ return qtrue;
+}
+
+// asset_shader <string>
+qboolean ItemParse_asset_shader( itemDef_t *item, int handle ) {
+ const char *temp;
+
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->asset = DC->registerShaderNoMip(temp);
+ return qtrue;
+}
+
+// model_origin <number> <number> <number>
+qboolean ItemParse_model_origin( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (PC_Float_Parse(handle, &modelPtr->origin[0])) {
+ if (PC_Float_Parse(handle, &modelPtr->origin[1])) {
+ if (PC_Float_Parse(handle, &modelPtr->origin[2])) {
+ return qtrue;
+ }
+ }
+ }
+ return qfalse;
+}
+
+// model_fovx <number>
+qboolean ItemParse_model_fovx( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Float_Parse(handle, &modelPtr->fov_x)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// model_fovy <number>
+qboolean ItemParse_model_fovy( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Float_Parse(handle, &modelPtr->fov_y)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// model_rotation <integer>
+qboolean ItemParse_model_rotation( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Int_Parse(handle, &modelPtr->rotationSpeed)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// model_angle <integer>
+qboolean ItemParse_model_angle( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Int_Parse(handle, &modelPtr->angle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// rect <rectangle>
+qboolean ItemParse_rect( itemDef_t *item, int handle ) {
+ if (!PC_Rect_Parse(handle, &item->window.rectClient)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// style <integer>
+qboolean ItemParse_style( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->window.style)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// decoration
+qboolean ItemParse_decoration( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_DECORATION;
+ return qtrue;
+}
+
+// notselectable
+qboolean ItemParse_notselectable( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+ Item_ValidateTypeData(item);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (item->type == ITEM_TYPE_LISTBOX && listPtr) {
+ listPtr->notselectable = qtrue;
+ }
+ return qtrue;
+}
+
+// manually wrapped
+qboolean ItemParse_wrapped( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_WRAPPED;
+ return qtrue;
+}
+
+// auto wrapped
+qboolean ItemParse_autowrapped( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_AUTOWRAPPED;
+ return qtrue;
+}
+
+
+// horizontalscroll
+qboolean ItemParse_horizontalscroll( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_HORIZONTAL;
+ return qtrue;
+}
+
+// type <integer>
+qboolean ItemParse_type( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->type)) {
+ return qfalse;
+ }
+ Item_ValidateTypeData(item);
+ return qtrue;
+}
+
+// elementwidth, used for listbox image elements
+// uses textalignx for storage
+qboolean ItemParse_elementwidth( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (!PC_Float_Parse(handle, &listPtr->elementWidth)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// elementheight, used for listbox image elements
+// uses textaligny for storage
+qboolean ItemParse_elementheight( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (!PC_Float_Parse(handle, &listPtr->elementHeight)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// feeder <float>
+qboolean ItemParse_feeder( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->special)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// elementtype, used to specify what type of elements a listbox contains
+// uses textstyle for storage
+qboolean ItemParse_elementtype( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (!PC_Int_Parse(handle, &listPtr->elementStyle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// columns sets a number of columns and an x pos and width per..
+qboolean ItemParse_columns( itemDef_t *item, int handle ) {
+ int num, i;
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (PC_Int_Parse(handle, &num)) {
+ if (num > MAX_LB_COLUMNS) {
+ num = MAX_LB_COLUMNS;
+ }
+ listPtr->numColumns = num;
+ for (i = 0; i < num; i++) {
+ int pos, width, maxChars, align;
+
+ if( PC_Int_Parse( handle, &pos ) &&
+ PC_Int_Parse( handle, &width ) &&
+ PC_Int_Parse( handle, &maxChars ) &&
+ PC_Int_Parse( handle, &align ) )
+ {
+ listPtr->columnInfo[i].pos = pos;
+ listPtr->columnInfo[i].width = width;
+ listPtr->columnInfo[i].maxChars = maxChars;
+ listPtr->columnInfo[i].align = align;
+ } else {
+ return qfalse;
+ }
+ }
+ } else {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_border( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->window.border)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_bordersize( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->window.borderSize)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_visible( itemDef_t *item, int handle ) {
+ int i;
+
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ if (i) {
+ item->window.flags |= WINDOW_VISIBLE;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_ownerdraw( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->window.ownerDraw)) {
+ return qfalse;
+ }
+ item->type = ITEM_TYPE_OWNERDRAW;
+ return qtrue;
+}
+
+qboolean ItemParse_align( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->alignment)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textalign( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->textalignment)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textalignx( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->textalignx)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textaligny( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->textaligny)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textscale( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->textscale)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textstyle( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->textStyle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_backcolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ item->window.backColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_forecolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ item->window.foreColor[i] = f;
+ item->window.flags |= WINDOW_FORECOLORSET;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_bordercolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ item->window.borderColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_outlinecolor( itemDef_t *item, int handle ) {
+ if (!PC_Color_Parse(handle, &item->window.outlineColor)){
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_background( itemDef_t *item, int handle ) {
+ const char *temp;
+
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->window.background = DC->registerShaderNoMip(temp);
+ return qtrue;
+}
+
+qboolean ItemParse_cinematic( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->window.cinematicName)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_doubleClick( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData) {
+ return qfalse;
+ }
+
+ listPtr = (listBoxDef_t*)item->typeData;
+
+ if (!PC_Script_Parse(handle, &listPtr->doubleClick)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_onFocus( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->onFocus)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_leaveFocus( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->leaveFocus)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseEnter( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseEnter)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseExit( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseExit)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseEnterText)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseExitText( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseExitText)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_onTextEntry( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->onTextEntry)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_action( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->action)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_special( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->special)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_cvarTest( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->cvarTest)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_cvar( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+
+ Item_ValidateTypeData(item);
+ if (!PC_String_Parse(handle, &item->cvar)) {
+ return qfalse;
+ }
+ if (item->typeData) {
+ editPtr = (editFieldDef_t*)item->typeData;
+ editPtr->minVal = -1;
+ editPtr->maxVal = -1;
+ editPtr->defVal = -1;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_maxChars( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+ int maxChars;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+
+ if (!PC_Int_Parse(handle, &maxChars)) {
+ return qfalse;
+ }
+ editPtr = (editFieldDef_t*)item->typeData;
+ editPtr->maxChars = maxChars;
+ return qtrue;
+}
+
+qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+ int maxChars;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+
+ if (!PC_Int_Parse(handle, &maxChars)) {
+ return qfalse;
+ }
+ editPtr = (editFieldDef_t*)item->typeData;
+ editPtr->maxPaintChars = maxChars;
+ return qtrue;
+}
+
+
+
+qboolean ItemParse_cvarFloat( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ editPtr = (editFieldDef_t*)item->typeData;
+ if (PC_String_Parse(handle, &item->cvar) &&
+ PC_Float_Parse(handle, &editPtr->defVal) &&
+ PC_Float_Parse(handle, &editPtr->minVal) &&
+ PC_Float_Parse(handle, &editPtr->maxVal)) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_cvarStrList( itemDef_t *item, int handle ) {
+ pc_token_t token;
+ multiDef_t *multiPtr;
+ int pass;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ multiPtr = (multiDef_t*)item->typeData;
+ multiPtr->count = 0;
+ multiPtr->strDef = qtrue;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+
+ pass = 0;
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu item\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ if (*token.string == ',' || *token.string == ';') {
+ continue;
+ }
+
+ if (pass == 0) {
+ multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string);
+ pass = 1;
+ } else {
+ multiPtr->cvarStr[multiPtr->count] = String_Alloc(token.string);
+ pass = 0;
+ multiPtr->count++;
+ if (multiPtr->count >= MAX_MULTI_CVARS) {
+ return qfalse;
+ }
+ }
+
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) {
+ pc_token_t token;
+ multiDef_t *multiPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ multiPtr = (multiDef_t*)item->typeData;
+ multiPtr->count = 0;
+ multiPtr->strDef = qfalse;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu item\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ if (*token.string == ',' || *token.string == ';') {
+ continue;
+ }
+
+ multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string);
+ if (!PC_Float_Parse(handle, &multiPtr->cvarValue[multiPtr->count])) {
+ return qfalse;
+ }
+
+ multiPtr->count++;
+ if (multiPtr->count >= MAX_MULTI_CVARS) {
+ return qfalse;
+ }
+
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+
+
+qboolean ItemParse_addColorRange( itemDef_t *item, int handle ) {
+ colorRangeDef_t color;
+
+ if (PC_Float_Parse(handle, &color.low) &&
+ PC_Float_Parse(handle, &color.high) &&
+ PC_Color_Parse(handle, &color.color) ) {
+ if (item->numColors < MAX_COLOR_RANGES) {
+ memcpy(&item->colorRanges[item->numColors], &color, sizeof(color));
+ item->numColors++;
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle ) {
+ int i;
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ item->window.ownerDrawFlags |= i;
+ return qtrue;
+}
+
+qboolean ItemParse_enableCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_ENABLE;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_disableCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_DISABLE;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_showCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_SHOW;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_hideCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_HIDE;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+keywordHash_t itemParseKeywords[] = {
+ {"name", ItemParse_name, NULL},
+ {"text", ItemParse_text, NULL},
+ {"group", ItemParse_group, NULL},
+ {"asset_model", ItemParse_asset_model, NULL},
+ {"asset_shader", ItemParse_asset_shader, NULL},
+ {"model_origin", ItemParse_model_origin, NULL},
+ {"model_fovx", ItemParse_model_fovx, NULL},
+ {"model_fovy", ItemParse_model_fovy, NULL},
+ {"model_rotation", ItemParse_model_rotation, NULL},
+ {"model_angle", ItemParse_model_angle, NULL},
+ {"rect", ItemParse_rect, NULL},
+ {"style", ItemParse_style, NULL},
+ {"decoration", ItemParse_decoration, NULL},
+ {"notselectable", ItemParse_notselectable, NULL},
+ {"wrapped", ItemParse_wrapped, NULL},
+ {"autowrapped", ItemParse_autowrapped, NULL},
+ {"horizontalscroll", ItemParse_horizontalscroll, NULL},
+ {"type", ItemParse_type, NULL},
+ {"elementwidth", ItemParse_elementwidth, NULL},
+ {"elementheight", ItemParse_elementheight, NULL},
+ {"feeder", ItemParse_feeder, NULL},
+ {"elementtype", ItemParse_elementtype, NULL},
+ {"columns", ItemParse_columns, NULL},
+ {"border", ItemParse_border, NULL},
+ {"bordersize", ItemParse_bordersize, NULL},
+ {"visible", ItemParse_visible, NULL},
+ {"ownerdraw", ItemParse_ownerdraw, NULL},
+ {"align", ItemParse_align, NULL},
+ {"textalign", ItemParse_textalign, NULL},
+ {"textalignx", ItemParse_textalignx, NULL},
+ {"textaligny", ItemParse_textaligny, NULL},
+ {"textscale", ItemParse_textscale, NULL},
+ {"textstyle", ItemParse_textstyle, NULL},
+ {"backcolor", ItemParse_backcolor, NULL},
+ {"forecolor", ItemParse_forecolor, NULL},
+ {"bordercolor", ItemParse_bordercolor, NULL},
+ {"outlinecolor", ItemParse_outlinecolor, NULL},
+ {"background", ItemParse_background, NULL},
+ {"onFocus", ItemParse_onFocus, NULL},
+ {"leaveFocus", ItemParse_leaveFocus, NULL},
+ {"mouseEnter", ItemParse_mouseEnter, NULL},
+ {"mouseExit", ItemParse_mouseExit, NULL},
+ {"mouseEnterText", ItemParse_mouseEnterText, NULL},
+ {"mouseExitText", ItemParse_mouseExitText, NULL},
+ {"onTextEntry", ItemParse_onTextEntry, NULL},
+ {"action", ItemParse_action, NULL},
+ {"special", ItemParse_special, NULL},
+ {"cvar", ItemParse_cvar, NULL},
+ {"maxChars", ItemParse_maxChars, NULL},
+ {"maxPaintChars", ItemParse_maxPaintChars, NULL},
+ {"focusSound", ItemParse_focusSound, NULL},
+ {"cvarFloat", ItemParse_cvarFloat, NULL},
+ {"cvarStrList", ItemParse_cvarStrList, NULL},
+ {"cvarFloatList", ItemParse_cvarFloatList, NULL},
+ {"addColorRange", ItemParse_addColorRange, NULL},
+ {"ownerdrawFlag", ItemParse_ownerdrawFlag, NULL},
+ {"enableCvar", ItemParse_enableCvar, NULL},
+ {"cvarTest", ItemParse_cvarTest, NULL},
+ {"disableCvar", ItemParse_disableCvar, NULL},
+ {"showCvar", ItemParse_showCvar, NULL},
+ {"hideCvar", ItemParse_hideCvar, NULL},
+ {"cinematic", ItemParse_cinematic, NULL},
+ {"doubleclick", ItemParse_doubleClick, NULL},
+ {NULL, voidFunction2, NULL}
+};
+
+keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE];
+
+/*
+===============
+Item_SetupKeywordHash
+===============
+*/
+void Item_SetupKeywordHash( void )
+{
+ int i;
+
+ memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) );
+
+ for( i = 0; itemParseKeywords[ i ].keyword; i++ )
+ KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[ i ] );
+}
+
+/*
+===============
+Item_Parse
+===============
+*/
+qboolean Item_Parse(int handle, itemDef_t *item) {
+ pc_token_t token;
+ keywordHash_t *key;
+
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu item\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ key = KeywordHash_Find(itemParseKeywordHash, token.string);
+ if (!key) {
+ PC_SourceError(handle, "unknown menu item keyword %s", token.string);
+ continue;
+ }
+ if ( !key->func(item, handle) ) {
+ PC_SourceError(handle, "couldn't parse menu item keyword %s", token.string);
+ return qfalse;
+ }
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+
+// Item_InitControls
+// init's special control types
+void Item_InitControls(itemDef_t *item) {
+ if (item == NULL) {
+ return;
+ }
+ if (item->type == ITEM_TYPE_LISTBOX) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+ item->cursorPos = 0;
+ if (listPtr) {
+ listPtr->cursorPos = 0;
+ listPtr->startPos = 0;
+ listPtr->endPos = 0;
+ listPtr->cursorPos = 0;
+ }
+ }
+}
+
+/*
+===============
+Menu Keyword Parse functions
+===============
+*/
+
+qboolean MenuParse_font( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_String_Parse(handle, &menu->font)) {
+ return qfalse;
+ }
+ if (!DC->Assets.fontRegistered) {
+ DC->registerFont(menu->font, 48, &DC->Assets.textFont);
+ DC->Assets.fontRegistered = qtrue;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_name( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_String_Parse(handle, &menu->window.name)) {
+ return qfalse;
+ }
+ if (Q_stricmp(menu->window.name, "main") == 0) {
+ // default main as having focus
+ //menu->window.flags |= WINDOW_HASFOCUS;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_fullscreen( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Int_Parse(handle, (int*) &menu->fullScreen)) { // bk001206 - cast qboolean
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_rect( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Rect_Parse(handle, &menu->window.rect)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_style( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Int_Parse(handle, &menu->window.style)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_visible( itemDef_t *item, int handle ) {
+ int i;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ if (i) {
+ menu->window.flags |= WINDOW_VISIBLE;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_onOpen( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Script_Parse(handle, &menu->onOpen)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_onClose( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Script_Parse(handle, &menu->onClose)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_onESC( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Script_Parse(handle, &menu->onESC)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+
+qboolean MenuParse_border( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Int_Parse(handle, &menu->window.border)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_borderSize( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Float_Parse(handle, &menu->window.borderSize)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_backcolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->window.backColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_forecolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->window.foreColor[i] = f;
+ menu->window.flags |= WINDOW_FORECOLORSET;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_bordercolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->window.borderColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_focuscolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->focusColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_disablecolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->disableColor[i] = f;
+ }
+ return qtrue;
+}
+
+
+qboolean MenuParse_outlinecolor( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Color_Parse(handle, &menu->window.outlineColor)){
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_background( itemDef_t *item, int handle ) {
+ const char *buff;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_String_Parse(handle, &buff)) {
+ return qfalse;
+ }
+ menu->window.background = DC->registerShaderNoMip(buff);
+ return qtrue;
+}
+
+qboolean MenuParse_cinematic( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_String_Parse(handle, &menu->window.cinematicName)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle ) {
+ int i;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ menu->window.ownerDrawFlags |= i;
+ return qtrue;
+}
+
+qboolean MenuParse_ownerdraw( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &menu->window.ownerDraw)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+// decoration
+qboolean MenuParse_popup( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ menu->window.flags |= WINDOW_POPUP;
+ return qtrue;
+}
+
+
+qboolean MenuParse_outOfBounds( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ menu->window.flags |= WINDOW_OOB_CLICK;
+ return qtrue;
+}
+
+qboolean MenuParse_soundLoop( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_String_Parse(handle, &menu->soundName)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_fadeClamp( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Float_Parse(handle, &menu->fadeClamp)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_fadeAmount( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Float_Parse(handle, &menu->fadeAmount)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+qboolean MenuParse_fadeCycle( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &menu->fadeCycle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+qboolean MenuParse_itemDef( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (menu->itemCount < MAX_MENUITEMS) {
+ menu->items[menu->itemCount] = UI_Alloc(sizeof(itemDef_t));
+ Item_Init(menu->items[menu->itemCount]);
+ if (!Item_Parse(handle, menu->items[menu->itemCount])) {
+ return qfalse;
+ }
+ Item_InitControls(menu->items[menu->itemCount]);
+ menu->items[menu->itemCount++]->parent = menu;
+ }
+ return qtrue;
+}
+
+keywordHash_t menuParseKeywords[] = {
+ {"font", MenuParse_font, NULL},
+ {"name", MenuParse_name, NULL},
+ {"fullscreen", MenuParse_fullscreen, NULL},
+ {"rect", MenuParse_rect, NULL},
+ {"style", MenuParse_style, NULL},
+ {"visible", MenuParse_visible, NULL},
+ {"onOpen", MenuParse_onOpen, NULL},
+ {"onClose", MenuParse_onClose, NULL},
+ {"onESC", MenuParse_onESC, NULL},
+ {"border", MenuParse_border, NULL},
+ {"borderSize", MenuParse_borderSize, NULL},
+ {"backcolor", MenuParse_backcolor, NULL},
+ {"forecolor", MenuParse_forecolor, NULL},
+ {"bordercolor", MenuParse_bordercolor, NULL},
+ {"focuscolor", MenuParse_focuscolor, NULL},
+ {"disablecolor", MenuParse_disablecolor, NULL},
+ {"outlinecolor", MenuParse_outlinecolor, NULL},
+ {"background", MenuParse_background, NULL},
+ {"ownerdraw", MenuParse_ownerdraw, NULL},
+ {"ownerdrawFlag", MenuParse_ownerdrawFlag, NULL},
+ {"outOfBoundsClick", MenuParse_outOfBounds, NULL},
+ {"soundLoop", MenuParse_soundLoop, NULL},
+ {"itemDef", MenuParse_itemDef, NULL},
+ {"cinematic", MenuParse_cinematic, NULL},
+ {"popup", MenuParse_popup, NULL},
+ {"fadeClamp", MenuParse_fadeClamp, NULL},
+ {"fadeCycle", MenuParse_fadeCycle, NULL},
+ {"fadeAmount", MenuParse_fadeAmount, NULL},
+ {NULL, voidFunction2, NULL}
+};
+
+keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE];
+
+/*
+===============
+Menu_SetupKeywordHash
+===============
+*/
+void Menu_SetupKeywordHash( void )
+{
+ int i;
+
+ memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) );
+
+ for(i = 0; menuParseKeywords[ i ].keyword; i++ )
+ KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[ i ] );
+}
+
+/*
+===============
+Menu_Parse
+===============
+*/
+qboolean Menu_Parse(int handle, menuDef_t *menu) {
+ pc_token_t token;
+ keywordHash_t *key;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+
+ memset(&token, 0, sizeof(pc_token_t));
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ key = KeywordHash_Find(menuParseKeywordHash, token.string);
+ if (!key) {
+ PC_SourceError(handle, "unknown menu keyword %s", token.string);
+ continue;
+ }
+ if ( !key->func((itemDef_t*)menu, handle) ) {
+ PC_SourceError(handle, "couldn't parse menu keyword %s", token.string);
+ return qfalse;
+ }
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+/*
+===============
+Menu_New
+===============
+*/
+void Menu_New(int handle) {
+ menuDef_t *menu = &Menus[menuCount];
+
+ if (menuCount < MAX_MENUS) {
+ Menu_Init(menu);
+ if (Menu_Parse(handle, menu)) {
+ Menu_PostParse(menu);
+ menuCount++;
+ }
+ }
+}
+
+int Menu_Count( void ) {
+ return menuCount;
+}
+
+void Menu_PaintAll( void ) {
+ int i;
+
+ if( g_editingField || g_waitingForKey )
+ DC->setCVar( "ui_hideCursor", "1" );
+ else
+ DC->setCVar( "ui_hideCursor", "0" );
+
+ if (captureFunc) {
+ captureFunc(captureData);
+ }
+
+ for (i = 0; i < Menu_Count(); i++) {
+ Menu_Paint(&Menus[i], qfalse);
+ }
+
+ if (debugMode) {
+ vec4_t v = {1, 1, 1, 1};
+ DC->drawText(5, 25, .5, v, va("fps: %f", DC->FPS), 0, 0, 0);
+ }
+}
+
+void Menu_Reset( void )
+{
+ menuCount = 0;
+}
+
+displayContextDef_t *Display_GetContext( void ) {
+ return DC;
+}
+
+void *Display_CaptureItem(int x, int y) {
+ int i;
+
+ for (i = 0; i < menuCount; i++) {
+ // turn off focus each item
+ // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
+ if (Rect_ContainsPoint(&Menus[i].window.rect, x, y)) {
+ return &Menus[i];
+ }
+ }
+ return NULL;
+}
+
+
+// FIXME:
+qboolean Display_MouseMove(void *p, int x, int y) {
+ int i;
+ menuDef_t *menu = p;
+
+ if (menu == NULL) {
+ menu = Menu_GetFocused();
+ if (menu) {
+ if (menu->window.flags & WINDOW_POPUP) {
+ Menu_HandleMouseMove(menu, x, y);
+ return qtrue;
+ }
+ }
+ for (i = 0; i < menuCount; i++) {
+ Menu_HandleMouseMove(&Menus[i], x, y);
+ }
+ } else {
+ menu->window.rect.x += x;
+ menu->window.rect.y += y;
+ Menu_UpdatePosition(menu);
+ }
+ return qtrue;
+
+}
+
+int Display_CursorType(int x, int y) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ rectDef_t r2;
+ r2.x = Menus[i].window.rect.x - 3;
+ r2.y = Menus[i].window.rect.y - 3;
+ r2.w = r2.h = 7;
+ if (Rect_ContainsPoint(&r2, x, y)) {
+ return CURSOR_SIZER;
+ }
+ }
+ return CURSOR_ARROW;
+}
+
+
+void Display_HandleKey(int key, qboolean down, int x, int y) {
+ menuDef_t *menu = Display_CaptureItem(x, y);
+ if (menu == NULL) {
+ menu = Menu_GetFocused();
+ }
+ if (menu) {
+ Menu_HandleKey(menu, key, down );
+ }
+}
+
+static void Window_CacheContents(windowDef_t *window) {
+ if (window) {
+ if (window->cinematicName) {
+ int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0);
+ DC->stopCinematic(cin);
+ }
+ }
+}
+
+
+static void Item_CacheContents(itemDef_t *item) {
+ if (item) {
+ Window_CacheContents(&item->window);
+ }
+
+}
+
+static void Menu_CacheContents(menuDef_t *menu) {
+ if (menu) {
+ int i;
+ Window_CacheContents(&menu->window);
+ for (i = 0; i < menu->itemCount; i++) {
+ Item_CacheContents(menu->items[i]);
+ }
+
+ if (menu->soundName && *menu->soundName) {
+ DC->registerSound(menu->soundName, qfalse);
+ }
+ }
+
+}
+
+void Display_CacheAll( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ Menu_CacheContents(&Menus[i]);
+ }
+}
+
+
+static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y) {
+ if (menu && menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)) {
+ if (Rect_ContainsPoint(&menu->window.rect, x, y)) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ // turn off focus each item
+ // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
+
+ if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) {
+ continue;
+ }
+
+ if (menu->items[i]->window.flags & WINDOW_DECORATION) {
+ continue;
+ }
+
+ if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) {
+ itemDef_t *overItem = menu->items[i];
+ if (overItem->type == ITEM_TYPE_TEXT && overItem->text) {
+ if (Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) {
+ return qtrue;
+ } else {
+ continue;
+ }
+ } else {
+ return qtrue;
+ }
+ }
+ }
+
+ }
+ }
+ return qfalse;
+}
+
diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h
new file mode 100644
index 0000000..210899e
--- /dev/null
+++ b/src/ui/ui_shared.h
@@ -0,0 +1,455 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef __UI_SHARED_H
+#define __UI_SHARED_H
+
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "../client/keycodes.h"
+
+#include "../ui/menudef.h"
+
+#define MAX_MENUNAME 32
+#define MAX_ITEMTEXT 64
+#define MAX_ITEMACTION 64
+#define MAX_MENUDEFFILE 4096
+#define MAX_MENUFILE 32768
+#define MAX_MENUS 256
+#define MAX_MENUITEMS 128
+#define MAX_COLOR_RANGES 10
+#define MAX_OPEN_MENUS 16
+
+#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive
+#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive
+#define WINDOW_VISIBLE 0x00000004 // is visible
+#define WINDOW_GREY 0x00000008 // is visible but grey ( non-active )
+#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc..
+#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active
+#define WINDOW_FADINGIN 0x00000040 // fading in
+#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive
+#define WINDOW_INTRANSITION 0x00000100 // window is in transition
+#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not )
+#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal
+#define WINDOW_LB_LEFTARROW 0x00000800 // mouse is over left/up arrow
+#define WINDOW_LB_RIGHTARROW 0x00001000 // mouse is over right/down arrow
+#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb
+#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up
+#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down
+#define WINDOW_ORBITING 0x00010000 // item is in orbit
+#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click
+#define WINDOW_WRAPPED 0x00040000 // manually wrap text
+#define WINDOW_AUTOWRAPPED 0x00080000 // auto wrap text
+#define WINDOW_FORCED 0x00100000 // forced open
+#define WINDOW_POPUP 0x00200000 // popup
+#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set
+#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented )
+
+
+// CGAME cursor type bits
+#define CURSOR_NONE 0x00000001
+#define CURSOR_ARROW 0x00000002
+#define CURSOR_SIZER 0x00000004
+
+#ifdef CGAME
+#define STRING_POOL_SIZE 128*1024
+#else
+#define STRING_POOL_SIZE 384*1024
+#endif
+#define MAX_STRING_HANDLES 4096
+
+#define MAX_SCRIPT_ARGS 12
+#define MAX_EDITFIELD 256
+
+#define ART_FX_BASE "menu/art/fx_base"
+#define ART_FX_BLUE "menu/art/fx_blue"
+#define ART_FX_CYAN "menu/art/fx_cyan"
+#define ART_FX_GREEN "menu/art/fx_grn"
+#define ART_FX_RED "menu/art/fx_red"
+#define ART_FX_TEAL "menu/art/fx_teal"
+#define ART_FX_WHITE "menu/art/fx_white"
+#define ART_FX_YELLOW "menu/art/fx_yel"
+
+#define ASSET_GRADIENTBAR "ui/assets/gradientbar2.tga"
+#define ASSET_SCROLLBAR "ui/assets/scrollbar.tga"
+#define ASSET_SCROLLBAR_ARROWDOWN "ui/assets/scrollbar_arrow_dwn_a.tga"
+#define ASSET_SCROLLBAR_ARROWUP "ui/assets/scrollbar_arrow_up_a.tga"
+#define ASSET_SCROLLBAR_ARROWLEFT "ui/assets/scrollbar_arrow_left.tga"
+#define ASSET_SCROLLBAR_ARROWRIGHT "ui/assets/scrollbar_arrow_right.tga"
+#define ASSET_SCROLL_THUMB "ui/assets/scrollbar_thumb.tga"
+#define ASSET_SLIDER_BAR "ui/assets/slider2.tga"
+#define ASSET_SLIDER_THUMB "ui/assets/sliderbutt_1.tga"
+#define SCROLLBAR_SIZE 16.0
+#define SLIDER_WIDTH 96.0
+#define SLIDER_HEIGHT 16.0
+#define SLIDER_THUMB_WIDTH 12.0
+#define SLIDER_THUMB_HEIGHT 20.0
+#define NUM_CROSSHAIRS 10
+
+typedef struct {
+ const char *command;
+ const char *args[MAX_SCRIPT_ARGS];
+} scriptDef_t;
+
+
+typedef struct {
+ float x; // horiz position
+ float y; // vert position
+ float w; // width
+ float h; // height;
+} rectDef_t;
+
+typedef rectDef_t Rectangle;
+
+// FIXME: do something to separate text vs window stuff
+typedef struct {
+ Rectangle rect; // client coord rectangle
+ Rectangle rectClient; // screen coord rectangle
+ const char *name; //
+ const char *group; // if it belongs to a group
+ const char *cinematicName; // cinematic name
+ int cinematic; // cinematic handle
+ int style; //
+ int border; //
+ int ownerDraw; // ownerDraw style
+ int ownerDrawFlags; // show flags for ownerdraw items
+ float borderSize; //
+ int flags; // visible, focus, mouseover, cursor
+ Rectangle rectEffects; // for various effects
+ Rectangle rectEffects2; // for various effects
+ int offsetTime; // time based value for various effects
+ int nextTime; // time next effect should cycle
+ vec4_t foreColor; // text color
+ vec4_t backColor; // border color
+ vec4_t borderColor; // border color
+ vec4_t outlineColor; // border color
+ qhandle_t background; // background asset
+} windowDef_t;
+
+typedef windowDef_t Window;
+
+typedef struct {
+ vec4_t color;
+ float low;
+ float high;
+} colorRangeDef_t;
+
+// FIXME: combine flags into bitfields to save space
+// FIXME: consolidate all of the common stuff in one structure for menus and items
+// THINKABOUTME: is there any compelling reason not to have items contain items
+// and do away with a menu per say.. major issue is not being able to dynamically allocate
+// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have
+// the engine just allocate the pool for it based on a cvar
+// many of the vars are re-used for different item types, as such they are not always named appropriately
+// the benefits of c++ in DOOM will greatly help crap like this
+// FIXME: need to put a type ptr that points to specific type info per type
+//
+#define MAX_LB_COLUMNS 16
+
+typedef struct columnInfo_s {
+ int pos;
+ int width;
+ int maxChars;
+ int align;
+} columnInfo_t;
+
+typedef struct listBoxDef_s {
+ int startPos;
+ int endPos;
+ int drawPadding;
+ int cursorPos;
+ float elementWidth;
+ float elementHeight;
+ int elementStyle;
+ int numColumns;
+ columnInfo_t columnInfo[MAX_LB_COLUMNS];
+ const char *doubleClick;
+ qboolean notselectable;
+} listBoxDef_t;
+
+typedef struct editFieldDef_s {
+ float minVal; // edit field limits
+ float maxVal; //
+ float defVal; //
+ float range; //
+ int maxChars; // for edit fields
+ int maxPaintChars; // for edit fields
+ int paintOffset; //
+} editFieldDef_t;
+
+#define MAX_MULTI_CVARS 32
+
+typedef struct multiDef_s {
+ const char *cvarList[MAX_MULTI_CVARS];
+ const char *cvarStr[MAX_MULTI_CVARS];
+ float cvarValue[MAX_MULTI_CVARS];
+ int count;
+ qboolean strDef;
+} multiDef_t;
+
+typedef struct modelDef_s {
+ int angle;
+ vec3_t origin;
+ float fov_x;
+ float fov_y;
+ int rotationSpeed;
+} modelDef_t;
+
+#define CVAR_ENABLE 0x00000001
+#define CVAR_DISABLE 0x00000002
+#define CVAR_SHOW 0x00000004
+#define CVAR_HIDE 0x00000008
+
+typedef struct itemDef_s {
+ Window window; // common positional, border, style, layout info
+ Rectangle textRect; // rectangle the text ( if any ) consumes
+ int type; // text, button, radiobutton, checkbox, textfield, listbox, combo
+ int alignment; // left center right
+ int textalignment; // ( optional ) alignment for text within rect based on text width
+ float textalignx; // ( optional ) text alignment x coord
+ float textaligny; // ( optional ) text alignment x coord
+ float textscale; // scale percentage from 72pts
+ int textStyle; // ( optional ) style, normal and shadowed are it for now
+ const char *text; // display text
+ void *parent; // menu owner
+ qhandle_t asset; // handle to asset
+ const char *mouseEnterText; // mouse enter script
+ const char *mouseExitText; // mouse exit script
+ const char *mouseEnter; // mouse enter script
+ const char *mouseExit; // mouse exit script
+ const char *action; // select script
+ const char *onFocus; // select script
+ const char *leaveFocus; // select script
+ const char *onTextEntry; // called when text entered
+ const char *cvar; // associated cvar
+ const char *cvarTest; // associated cvar for enable actions
+ const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list
+ int cvarFlags; // what type of action to take on cvarenables
+ sfxHandle_t focusSound;
+ int numColors; // number of color ranges
+ colorRangeDef_t colorRanges[MAX_COLOR_RANGES];
+ float special; // used for feeder id's etc.. diff per type
+ int cursorPos; // cursor position in characters
+ void *typeData; // type specific data ptr's
+} itemDef_t;
+
+typedef struct {
+ Window window;
+ const char *font; // font
+ qboolean fullScreen; // covers entire screen
+ int itemCount; // number of items;
+ int fontIndex; //
+ int cursorItem; // which item as the cursor
+ int fadeCycle; //
+ float fadeClamp; //
+ float fadeAmount; //
+ const char *onOpen; // run when the menu is first opened
+ const char *onClose; // run when the menu is closed
+ const char *onESC; // run when the menu is closed
+ const char *soundName; // background loop sound for menu
+
+ vec4_t focusColor; // focus color for items
+ vec4_t disableColor; // focus color for items
+ itemDef_t *items[MAX_MENUITEMS]; // items this menu contains
+} menuDef_t;
+
+typedef struct {
+ const char *fontStr;
+ const char *cursorStr;
+ const char *gradientStr;
+ fontInfo_t textFont;
+ fontInfo_t smallFont;
+ fontInfo_t bigFont;
+ qhandle_t cursor;
+ qhandle_t gradientBar;
+ qhandle_t scrollBarArrowUp;
+ qhandle_t scrollBarArrowDown;
+ qhandle_t scrollBarArrowLeft;
+ qhandle_t scrollBarArrowRight;
+ qhandle_t scrollBar;
+ qhandle_t scrollBarThumb;
+ qhandle_t buttonMiddle;
+ qhandle_t buttonInside;
+ qhandle_t solidBox;
+ qhandle_t sliderBar;
+ qhandle_t sliderThumb;
+ sfxHandle_t menuEnterSound;
+ sfxHandle_t menuExitSound;
+ sfxHandle_t menuBuzzSound;
+ sfxHandle_t itemFocusSound;
+ float fadeClamp;
+ int fadeCycle;
+ float fadeAmount;
+ float shadowX;
+ float shadowY;
+ vec4_t shadowColor;
+ float shadowFadeClamp;
+ qboolean fontRegistered;
+
+} cachedAssets_t;
+
+typedef struct {
+ const char *name;
+ void (*handler) (itemDef_t *item, char** args);
+} commandDef_t;
+
+typedef struct {
+ qhandle_t (*registerShaderNoMip) (const char *p);
+ void (*setColor) (const vec4_t v);
+ void (*drawHandlePic) (float x, float y, float w, float h, qhandle_t asset);
+ void (*drawStretchPic) (float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader );
+ void (*drawText) (float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style );
+ int (*textWidth) (const char *text, float scale, int limit);
+ int (*textHeight) (const char *text, float scale, int limit);
+ qhandle_t (*registerModel) (const char *p);
+ void (*modelBounds) (qhandle_t model, vec3_t min, vec3_t max);
+ void (*fillRect) ( float x, float y, float w, float h, const vec4_t color);
+ void (*drawRect) ( float x, float y, float w, float h, float size, const vec4_t color);
+ void (*drawSides) (float x, float y, float w, float h, float size);
+ void (*drawTopBottom) (float x, float y, float w, float h, float size);
+ void (*clearScene) (void);
+ void (*addRefEntityToScene) (const refEntity_t *re );
+ void (*renderScene) ( const refdef_t *fd );
+ void (*registerFont) (const char *pFontname, int pointSize, fontInfo_t *font);
+ void (*ownerDrawItem) (float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle);
+ float (*getValue) (int ownerDraw);
+ qboolean (*ownerDrawVisible) (int flags);
+ void (*runScript)(char **p);
+ void (*getTeamColor)(vec4_t *color);
+ void (*getCVarString)(const char *cvar, char *buffer, int bufsize);
+ float (*getCVarValue)(const char *cvar);
+ void (*setCVar)(const char *cvar, const char *value);
+ void (*drawTextWithCursor)(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style);
+ void (*setOverstrikeMode)(qboolean b);
+ qboolean (*getOverstrikeMode)( void );
+ void (*startLocalSound)( sfxHandle_t sfx, int channelNum );
+ qboolean (*ownerDrawHandleKey)(int ownerDraw, int flags, float *special, int key);
+ int (*feederCount)(float feederID);
+ const char *(*feederItemText)(float feederID, int index, int column, qhandle_t *handle);
+ qhandle_t (*feederItemImage)(float feederID, int index);
+ void (*feederSelection)(float feederID, int index);
+ void (*keynumToStringBuf)( int keynum, char *buf, int buflen );
+ void (*getBindingBuf)( int keynum, char *buf, int buflen );
+ void (*setBinding)( int keynum, const char *binding );
+ void (*executeText)(int exec_when, const char *text );
+ void (*Error)(int level, const char *error, ...);
+ void (*Print)(const char *msg, ...);
+ void (*Pause)(qboolean b);
+ int (*ownerDrawWidth)(int ownerDraw, float scale);
+ sfxHandle_t (*registerSound)(const char *name, qboolean compressed);
+ void (*startBackgroundTrack)( const char *intro, const char *loop);
+ void (*stopBackgroundTrack)( void );
+ int (*playCinematic)(const char *name, float x, float y, float w, float h);
+ void (*stopCinematic)(int handle);
+ void (*drawCinematic)(int handle, float x, float y, float w, float h);
+ void (*runCinematicFrame)(int handle);
+
+ float yscale;
+ float xscale;
+ float bias;
+ int realTime;
+ int frameTime;
+ int cursorx;
+ int cursory;
+ qboolean debug;
+
+ cachedAssets_t Assets;
+
+ glconfig_t glconfig;
+ qhandle_t whiteShader;
+ qhandle_t gradientImage;
+ qhandle_t cursor;
+ float FPS;
+
+} displayContextDef_t;
+
+const char *String_Alloc(const char *p);
+void String_Init( void );
+void String_Report( void );
+void Init_Display(displayContextDef_t *dc);
+void Display_ExpandMacros(char * buff);
+void Menu_Init(menuDef_t *menu);
+void Item_Init(itemDef_t *item);
+void Menu_PostParse(menuDef_t *menu);
+menuDef_t *Menu_GetFocused( void );
+void Menu_HandleKey(menuDef_t *menu, int key, qboolean down);
+void Menu_HandleMouseMove(menuDef_t *menu, float x, float y);
+void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down);
+qboolean Float_Parse(char **p, float *f);
+qboolean Color_Parse(char **p, vec4_t *c);
+qboolean Int_Parse(char **p, int *i);
+qboolean Rect_Parse(char **p, rectDef_t *r);
+qboolean String_Parse(char **p, const char **out);
+qboolean Script_Parse(char **p, const char **out);
+qboolean PC_Float_Parse(int handle, float *f);
+qboolean PC_Color_Parse(int handle, vec4_t *c);
+qboolean PC_Int_Parse(int handle, int *i);
+qboolean PC_Rect_Parse(int handle, rectDef_t *r);
+qboolean PC_String_Parse(int handle, const char **out);
+qboolean PC_Script_Parse(int handle, const char **out);
+int Menu_Count( void );
+void Menu_New(int handle);
+void Menu_PaintAll( void );
+menuDef_t *Menus_ActivateByName(const char *p);
+void Menu_Reset( void );
+qboolean Menus_AnyFullScreenVisible( void );
+void Menus_Activate(menuDef_t *menu);
+
+displayContextDef_t *Display_GetContext( void );
+void *Display_CaptureItem(int x, int y);
+qboolean Display_MouseMove(void *p, int x, int y);
+int Display_CursorType(int x, int y);
+qboolean Display_KeyBindPending( void );
+void Menus_OpenByName(const char *p);
+menuDef_t *Menus_FindByName(const char *p);
+void Menus_ShowByName(const char *p);
+void Menus_CloseByName(const char *p);
+void Display_HandleKey(int key, qboolean down, int x, int y);
+void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t);
+void Menus_CloseAll( void );
+void Menu_Paint(menuDef_t *menu, qboolean forcePaint);
+void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name);
+void Display_CacheAll( void );
+
+void *UI_Alloc( int size );
+void UI_InitMemory( void );
+qboolean UI_OutOfMemory( void );
+
+void Controls_GetConfig( void );
+void Controls_SetConfig(qboolean restart);
+void Controls_SetDefaults( void );
+
+//for cg_draw.c
+void Item_Text_AutoWrapped_Paint( itemDef_t *item );
+
+int trap_Parse_AddGlobalDefine( char *define );
+int trap_Parse_LoadSource( const char *filename );
+int trap_Parse_FreeSource( int handle );
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token );
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line );
+
+void BindingFromName( const char *cvar );
+extern char g_nameBind1[ 32 ];
+extern char g_nameBind2[ 32 ];
+#endif
diff --git a/src/ui/ui_syscalls.asm b/src/ui/ui_syscalls.asm
new file mode 100644
index 0000000..1e797a9
--- /dev/null
+++ b/src/ui/ui_syscalls.asm
@@ -0,0 +1,102 @@
+code
+
+equ trap_Error -1
+equ trap_Print -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Set -4
+equ trap_Cvar_VariableValue -5
+equ trap_Cvar_VariableStringBuffer -6
+equ trap_Cvar_SetValue -7
+equ trap_Cvar_Reset -8
+equ trap_Cvar_Create -9
+equ trap_Cvar_InfoStringBuffer -10
+equ trap_Argc -11
+equ trap_Argv -12
+equ trap_Cmd_ExecuteText -13
+equ trap_FS_FOpenFile -14
+equ trap_FS_Read -15
+equ trap_FS_Write -16
+equ trap_FS_FCloseFile -17
+equ trap_FS_GetFileList -18
+equ trap_R_RegisterModel -19
+equ trap_R_RegisterSkin -20
+equ trap_R_RegisterShaderNoMip -21
+equ trap_R_ClearScene -22
+equ trap_R_AddRefEntityToScene -23
+equ trap_R_AddPolyToScene -24
+equ trap_R_AddLightToScene -25
+equ trap_R_RenderScene -26
+equ trap_R_SetColor -27
+equ trap_R_SetClipRegion -28
+equ trap_R_DrawStretchPic -29
+equ trap_UpdateScreen -30
+equ trap_CM_LerpTag -31
+equ trap_CM_LoadModel -32
+equ trap_S_RegisterSound -33
+equ trap_S_StartLocalSound -34
+equ trap_Key_KeynumToStringBuf -35
+equ trap_Key_GetBindingBuf -36
+equ trap_Key_SetBinding -37
+equ trap_Key_IsDown -38
+equ trap_Key_GetOverstrikeMode -39
+equ trap_Key_SetOverstrikeMode -40
+equ trap_Key_ClearStates -41
+equ trap_Key_GetCatcher -42
+equ trap_Key_SetCatcher -43
+equ trap_GetClipboardData -44
+equ trap_GetGlconfig -45
+equ trap_GetClientState -46
+equ trap_GetConfigString -47
+equ trap_LAN_GetPingQueueCount -48
+equ trap_LAN_ClearPing -49
+equ trap_LAN_GetPing -50
+equ trap_LAN_GetPingInfo -51
+equ trap_Cvar_Register -52
+equ trap_Cvar_Update -53
+equ trap_MemoryRemaining -54
+equ trap_R_RegisterFont -55
+equ trap_R_ModelBounds -56
+equ trap_S_StopBackgroundTrack -57
+equ trap_S_StartBackgroundTrack -58
+equ trap_RealTime -59
+equ trap_LAN_GetServerCount -60
+equ trap_LAN_GetServerAddressString -61
+equ trap_LAN_GetServerInfo -62
+equ trap_LAN_MarkServerVisible -63
+equ trap_LAN_UpdateVisiblePings -64
+equ trap_LAN_ResetPings -65
+equ trap_LAN_LoadCachedServers -66
+equ trap_LAN_SaveCachedServers -67
+equ trap_LAN_AddServer -68
+equ trap_LAN_RemoveServer -69
+equ trap_CIN_PlayCinematic -70
+equ trap_CIN_StopCinematic -71
+equ trap_CIN_RunCinematic -72
+equ trap_CIN_DrawCinematic -73
+equ trap_CIN_SetExtents -74
+equ trap_R_RemapShader -75
+equ trap_LAN_ServerStatus -76
+equ trap_LAN_GetServerPing -77
+equ trap_LAN_ServerIsVisible -78
+equ trap_LAN_CompareServers -79
+equ trap_FS_Seek -80
+equ trap_SetPbClStatus -81
+
+equ trap_Parse_AddGlobalDefine -82
+equ trap_Parse_LoadSource -83
+equ trap_Parse_FreeSource -84
+equ trap_Parse_ReadToken -85
+equ trap_Parse_SourceFileAndLine -86
+
+equ trap_GetNews -87
+
+equ memset -101
+equ memcpy -102
+equ strncpy -103
+equ sin -104
+equ cos -105
+equ atan2 -106
+equ sqrt -107
+equ floor -108
+equ ceil -109
+
diff --git a/src/ui/ui_syscalls.c b/src/ui/ui_syscalls.c
new file mode 100644
index 0000000..a27e573
--- /dev/null
+++ b/src/ui/ui_syscalls.c
@@ -0,0 +1,387 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "ui_local.h"
+
+// this file is only included when building a dll
+// syscalls.asm is included instead when building a qvm
+
+static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1;
+
+Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) {
+ syscall = syscallptr;
+}
+
+int PASSFLOAT( float x ) {
+ float floatTemp;
+ floatTemp = x;
+ return *(int *)&floatTemp;
+}
+
+void trap_Print( const char *string ) {
+ syscall( UI_PRINT, string );
+}
+
+void trap_Error( const char *string ) {
+ syscall( UI_ERROR, string );
+}
+
+int trap_Milliseconds( void ) {
+ return syscall( UI_MILLISECONDS );
+}
+
+void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) {
+ syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags );
+}
+
+void trap_Cvar_Update( vmCvar_t *cvar ) {
+ syscall( UI_CVAR_UPDATE, cvar );
+}
+
+void trap_Cvar_Set( const char *var_name, const char *value ) {
+ syscall( UI_CVAR_SET, var_name, value );
+}
+
+float trap_Cvar_VariableValue( const char *var_name ) {
+ int temp;
+ temp = syscall( UI_CVAR_VARIABLEVALUE, var_name );
+ return (*(float*)&temp);
+}
+
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) {
+ syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize );
+}
+
+void trap_Cvar_SetValue( const char *var_name, float value ) {
+ syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) );
+}
+
+void trap_Cvar_Reset( const char *name ) {
+ syscall( UI_CVAR_RESET, name );
+}
+
+void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ) {
+ syscall( UI_CVAR_CREATE, var_name, var_value, flags );
+}
+
+void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ) {
+ syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize );
+}
+
+int trap_Argc( void ) {
+ return syscall( UI_ARGC );
+}
+
+void trap_Argv( int n, char *buffer, int bufferLength ) {
+ syscall( UI_ARGV, n, buffer, bufferLength );
+}
+
+void trap_Cmd_ExecuteText( int exec_when, const char *text ) {
+ syscall( UI_CMD_EXECUTETEXT, exec_when, text );
+}
+
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
+ return syscall( UI_FS_FOPENFILE, qpath, f, mode );
+}
+
+void trap_FS_Read( void *buffer, int len, fileHandle_t f ) {
+ syscall( UI_FS_READ, buffer, len, f );
+}
+
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) {
+ syscall( UI_FS_WRITE, buffer, len, f );
+}
+
+void trap_FS_FCloseFile( fileHandle_t f ) {
+ syscall( UI_FS_FCLOSEFILE, f );
+}
+
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) {
+ return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize );
+}
+
+int trap_FS_Seek( fileHandle_t f, long offset, int origin ) {
+ return syscall( UI_FS_SEEK, f, offset, origin );
+}
+
+qhandle_t trap_R_RegisterModel( const char *name ) {
+ return syscall( UI_R_REGISTERMODEL, name );
+}
+
+qhandle_t trap_R_RegisterSkin( const char *name ) {
+ return syscall( UI_R_REGISTERSKIN, name );
+}
+
+void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) {
+ syscall( UI_R_REGISTERFONT, fontName, pointSize, font );
+}
+
+qhandle_t trap_R_RegisterShaderNoMip( const char *name ) {
+ return syscall( UI_R_REGISTERSHADERNOMIP, name );
+}
+
+void trap_R_ClearScene( void ) {
+ syscall( UI_R_CLEARSCENE );
+}
+
+void trap_R_AddRefEntityToScene( const refEntity_t *re ) {
+ syscall( UI_R_ADDREFENTITYTOSCENE, re );
+}
+
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) {
+ syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts );
+}
+
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
+ syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) );
+}
+
+void trap_R_RenderScene( const refdef_t *fd ) {
+ syscall( UI_R_RENDERSCENE, fd );
+}
+
+void trap_R_SetColor( const float *rgba ) {
+ syscall( UI_R_SETCOLOR, rgba );
+}
+
+void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) {
+ syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader );
+}
+
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) {
+ syscall( UI_R_MODELBOUNDS, model, mins, maxs );
+}
+
+void trap_UpdateScreen( void ) {
+ syscall( UI_UPDATESCREEN );
+}
+
+int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ) {
+ return syscall( UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName );
+}
+
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) {
+ syscall( UI_S_STARTLOCALSOUND, sfx, channelNum );
+}
+
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) {
+ return syscall( UI_S_REGISTERSOUND, sample, compressed );
+}
+
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) {
+ syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) {
+ syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_SetBinding( int keynum, const char *binding ) {
+ syscall( UI_KEY_SETBINDING, keynum, binding );
+}
+
+qboolean trap_Key_IsDown( int keynum ) {
+ return syscall( UI_KEY_ISDOWN, keynum );
+}
+
+qboolean trap_Key_GetOverstrikeMode( void ) {
+ return syscall( UI_KEY_GETOVERSTRIKEMODE );
+}
+
+void trap_Key_SetOverstrikeMode( qboolean state ) {
+ syscall( UI_KEY_SETOVERSTRIKEMODE, state );
+}
+
+void trap_Key_ClearStates( void ) {
+ syscall( UI_KEY_CLEARSTATES );
+}
+
+int trap_Key_GetCatcher( void ) {
+ return syscall( UI_KEY_GETCATCHER );
+}
+
+void trap_Key_SetCatcher( int catcher ) {
+ syscall( UI_KEY_SETCATCHER, catcher );
+}
+
+void trap_GetClipboardData( char *buf, int bufsize ) {
+ syscall( UI_GETCLIPBOARDDATA, buf, bufsize );
+}
+
+void trap_GetClientState( uiClientState_t *state ) {
+ syscall( UI_GETCLIENTSTATE, state );
+}
+
+void trap_GetGlconfig( glconfig_t *glconfig ) {
+ syscall( UI_GETGLCONFIG, glconfig );
+}
+
+int trap_GetConfigString( int index, char* buff, int buffsize ) {
+ return syscall( UI_GETCONFIGSTRING, index, buff, buffsize );
+}
+
+int trap_LAN_GetServerCount( int source ) {
+ return syscall( UI_LAN_GETSERVERCOUNT, source );
+}
+
+void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) {
+ syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen );
+}
+
+void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ) {
+ syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen );
+}
+
+int trap_LAN_GetServerPing( int source, int n ) {
+ return syscall( UI_LAN_GETSERVERPING, source, n );
+}
+
+int trap_LAN_GetPingQueueCount( void ) {
+ return syscall( UI_LAN_GETPINGQUEUECOUNT );
+}
+
+int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ) {
+ return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen );
+}
+
+void trap_LAN_SaveCachedServers( void ) {
+ syscall( UI_LAN_SAVECACHEDSERVERS );
+}
+
+void trap_LAN_LoadCachedServers( void ) {
+ syscall( UI_LAN_LOADCACHEDSERVERS );
+}
+
+void trap_LAN_ResetPings(int n) {
+ syscall( UI_LAN_RESETPINGS, n );
+}
+
+void trap_LAN_ClearPing( int n ) {
+ syscall( UI_LAN_CLEARPING, n );
+}
+
+void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) {
+ syscall( UI_LAN_GETPING, n, buf, buflen, pingtime );
+}
+
+void trap_LAN_GetPingInfo( int n, char *buf, int buflen ) {
+ syscall( UI_LAN_GETPINGINFO, n, buf, buflen );
+}
+
+void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ) {
+ syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible );
+}
+
+int trap_LAN_ServerIsVisible( int source, int n) {
+ return syscall( UI_LAN_SERVERISVISIBLE, source, n );
+}
+
+qboolean trap_LAN_UpdateVisiblePings( int source ) {
+ return syscall( UI_LAN_UPDATEVISIBLEPINGS, source );
+}
+
+int trap_LAN_AddServer(int source, const char *name, const char *addr) {
+ return syscall( UI_LAN_ADDSERVER, source, name, addr );
+}
+
+void trap_LAN_RemoveServer(int source, const char *addr) {
+ syscall( UI_LAN_REMOVESERVER, source, addr );
+}
+
+int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) {
+ return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 );
+}
+
+int trap_MemoryRemaining( void ) {
+ return syscall( UI_MEMORY_REMAINING );
+}
+
+int trap_Parse_AddGlobalDefine( char *define ) {
+ return syscall( UI_PARSE_ADD_GLOBAL_DEFINE, define );
+}
+
+int trap_Parse_LoadSource( const char *filename ) {
+ return syscall( UI_PARSE_LOAD_SOURCE, filename );
+}
+
+int trap_Parse_FreeSource( int handle ) {
+ return syscall( UI_PARSE_FREE_SOURCE, handle );
+}
+
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) {
+ return syscall( UI_PARSE_READ_TOKEN, handle, pc_token );
+}
+
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) {
+ return syscall( UI_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line );
+}
+
+void trap_S_StopBackgroundTrack( void ) {
+ syscall( UI_S_STOPBACKGROUNDTRACK );
+}
+
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop) {
+ syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop );
+}
+
+int trap_RealTime(qtime_t *qtime) {
+ return syscall( UI_REAL_TIME, qtime );
+}
+
+// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate)
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) {
+ return syscall(UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits);
+}
+
+// stops playing the cinematic and ends it. should always return FMV_EOF
+// cinematics must be stopped in reverse order of when they are started
+e_status trap_CIN_StopCinematic(int handle) {
+ return syscall(UI_CIN_STOPCINEMATIC, handle);
+}
+
+
+// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached.
+e_status trap_CIN_RunCinematic (int handle) {
+ return syscall(UI_CIN_RUNCINEMATIC, handle);
+}
+
+
+// draws the current frame
+void trap_CIN_DrawCinematic (int handle) {
+ syscall(UI_CIN_DRAWCINEMATIC, handle);
+}
+
+
+// allows you to resize the animation dynamically
+void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) {
+ syscall(UI_CIN_SETEXTENTS, handle, x, y, w, h);
+}
+
+
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) {
+ syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset );
+}
+
+void trap_SetPbClStatus( int status ) {
+ syscall( UI_SET_PBCLSTATUS, status );
+}
diff --git a/src/ui/ui_syscalls_11.asm b/src/ui/ui_syscalls_11.asm
new file mode 100644
index 0000000..64d2ca3
--- /dev/null
+++ b/src/ui/ui_syscalls_11.asm
@@ -0,0 +1,98 @@
+code
+
+equ trap_Error -1
+equ trap_Print -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Set -4
+equ trap_Cvar_VariableValue -5
+equ trap_Cvar_VariableStringBuffer -6
+equ trap_Cvar_SetValue -7
+equ trap_Cvar_Reset -8
+equ trap_Cvar_Create -9
+equ trap_Cvar_InfoStringBuffer -10
+equ trap_Argc -11
+equ trap_Argv -12
+equ trap_Cmd_ExecuteText -13
+equ trap_FS_FOpenFile -14
+equ trap_FS_Read -15
+equ trap_FS_Write -16
+equ trap_FS_FCloseFile -17
+equ trap_FS_GetFileList -18
+equ trap_R_RegisterModel -19
+equ trap_R_RegisterSkin -20
+equ trap_R_RegisterShaderNoMip -21
+equ trap_R_ClearScene -22
+equ trap_R_AddRefEntityToScene -23
+equ trap_R_AddPolyToScene -24
+equ trap_R_AddLightToScene -25
+equ trap_R_RenderScene -26
+equ trap_R_SetColor -27
+equ trap_R_DrawStretchPic -28
+equ trap_UpdateScreen -29
+equ trap_CM_LerpTag -30
+equ trap_CM_LoadModel -31
+equ trap_S_RegisterSound -32
+equ trap_S_StartLocalSound -33
+equ trap_Key_KeynumToStringBuf -34
+equ trap_Key_GetBindingBuf -35
+equ trap_Key_SetBinding -36
+equ trap_Key_IsDown -37
+equ trap_Key_GetOverstrikeMode -38
+equ trap_Key_SetOverstrikeMode -39
+equ trap_Key_ClearStates -40
+equ trap_Key_GetCatcher -41
+equ trap_Key_SetCatcher -42
+equ trap_GetClipboardData -43
+equ trap_GetGlconfig -44
+equ trap_GetClientState -45
+equ trap_GetConfigString -46
+equ trap_LAN_GetPingQueueCount -47
+equ trap_LAN_ClearPing -48
+equ trap_LAN_GetPing -49
+equ trap_LAN_GetPingInfo -50
+equ trap_Cvar_Register -51
+equ trap_Cvar_Update -52
+equ trap_MemoryRemaining -53
+equ trap_R_RegisterFont -54
+equ trap_R_ModelBounds -55
+equ trap_Parse_AddGlobalDefine -56
+equ trap_Parse_LoadSource -57
+equ trap_Parse_FreeSource -58
+equ trap_Parse_ReadToken -59
+equ trap_Parse_SourceFileAndLine -60
+equ trap_S_StopBackgroundTrack -61
+equ trap_S_StartBackgroundTrack -62
+equ trap_RealTime -63
+equ trap_LAN_GetServerCount -64
+equ trap_LAN_GetServerAddressString -65
+equ trap_LAN_GetServerInfo -66
+equ trap_LAN_MarkServerVisible -67
+equ trap_LAN_UpdateVisiblePings -68
+equ trap_LAN_ResetPings -69
+equ trap_LAN_LoadCachedServers -70
+equ trap_LAN_SaveCachedServers -71
+equ trap_LAN_AddServer -72
+equ trap_LAN_RemoveServer -73
+equ trap_CIN_PlayCinematic -74
+equ trap_CIN_StopCinematic -75
+equ trap_CIN_RunCinematic -76
+equ trap_CIN_DrawCinematic -77
+equ trap_CIN_SetExtents -78
+equ trap_R_RemapShader -79
+equ trap_LAN_ServerStatus -80
+equ trap_LAN_GetServerPing -81
+equ trap_LAN_ServerIsVisible -82
+equ trap_LAN_CompareServers -83
+equ trap_FS_Seek -84
+equ trap_SetPbClStatus -85
+
+equ memset -101
+equ memcpy -102
+equ strncpy -103
+equ sin -104
+equ cos -105
+equ atan2 -106
+equ sqrt -107
+equ floor -108
+equ ceil -109
+
diff --git a/ui/assets/alien/buildstat.cfg b/ui/assets/alien/buildstat.cfg
new file mode 100644
index 0000000..1114302
--- /dev/null
+++ b/ui/assets/alien/buildstat.cfg
@@ -0,0 +1,37 @@
+// config for the building status indicators that builders see
+// NOTES:
+// * all characters (text/icons) are square
+// * character size is derived totally from frameHeight and vertialMargin
+// * healthPadding is NOT used compensated for in the margins
+
+frameShader "ui/assets/alien/buildstat/frame"
+frameWidth 150
+frameHeight 30
+
+healthPadding 2
+healthSevereColor 0.24 0.02 0.02 1
+healthHighColor 0.32 0.04 0.04 1
+healthElevatedColor 0.40 0.06 0.06 1
+healthGuardedColor 0.48 0.08 0.08 1
+healthLowColor 0.56 0.10 0.10 1
+
+// this gets drawn over frame and health, but numbers and icons go on top of it
+overlayShader "ui/assets/alien/buildstat/overlay"
+overlayWidth 156
+overlayHeight 36
+
+// PERCENT of frameHeight to use for top/bottom margin of icons/text
+// value is for total of top and bottom margins
+// valid values between 0.0 and 1.0
+verticalMargin 0.5
+
+// number of CHARS worth of space that should be used for left/right margins
+// value is for one side only
+// char width is determined by frameHeight and verticalMargin
+horizontalMargin 1.0
+
+markedShader "ui/assets/alien/buildstat/mark"
+noPowerShader "ui/assets/alien/buildstat/nopower"
+
+backColor 1.0 1.0 1.0 1
+foreColor 0.0 0.0 0.0 1
diff --git a/ui/assets/alien/buildstat/frame.tga b/ui/assets/alien/buildstat/frame.tga
new file mode 100644
index 0000000..3b1e1f5
--- /dev/null
+++ b/ui/assets/alien/buildstat/frame.tga
Binary files differ
diff --git a/ui/assets/alien/buildstat/mark.tga b/ui/assets/alien/buildstat/mark.tga
new file mode 100644
index 0000000..ef9123c
--- /dev/null
+++ b/ui/assets/alien/buildstat/mark.tga
Binary files differ
diff --git a/ui/assets/alien/buildstat/nopower.tga b/ui/assets/alien/buildstat/nopower.tga
new file mode 100644
index 0000000..a0c66c2
--- /dev/null
+++ b/ui/assets/alien/buildstat/nopower.tga
Binary files differ
diff --git a/ui/assets/alien/buildstat/overlay.tga b/ui/assets/alien/buildstat/overlay.tga
new file mode 100644
index 0000000..64a2358
--- /dev/null
+++ b/ui/assets/alien/buildstat/overlay.tga
Binary files differ
diff --git a/ui/assets/human/buildstat.cfg b/ui/assets/human/buildstat.cfg
new file mode 100644
index 0000000..9c192de
--- /dev/null
+++ b/ui/assets/human/buildstat.cfg
@@ -0,0 +1,39 @@
+// config for the building status indicators that builders see
+// NOTES:
+// * all characters (text/icons) are square
+// * character size is derived totally from frameHeight and vertialMargin
+// * healthPadding is NOT used compensated for in the margins
+
+frameShader "ui/assets/human/buildstat/frame"
+frameWidth 150
+frameHeight 30
+
+healthPadding 2
+
+// Homeworld Security Advisory System
+healthSevereColor 0.83 0.03 0.02 1
+healthHighColor 0.84 0.48 0.03 1
+healthElevatedColor 0.82 0.82 0.00 1
+healthGuardedColor 0.19 0.65 0.00 1
+healthLowColor 0.27 0.49 0.55 1
+
+// this gets drawn over frame and health, but numbers and icons go on top of it
+overlayShader ""
+overlayWidth 160
+overlayHeight 40
+
+// PERCENT of frameHeight to use for top/bottom margin of icons/text
+// value is for total of top and bottom margins
+// valid values between 0.0 and 1.0
+verticalMargin 0.5
+
+// number of CHARS worth of space that should be used for left/right margins
+// value is for one side only
+// char width is determined by frameHeight and verticalMargin
+horizontalMargin 1.0
+
+markedShader "ui/assets/human/buildstat/mark"
+noPowerShader "ui/assets/human/buildstat/nopower"
+
+backColor 1.0 1.0 1.0 1
+foreColor 0.0 0.0 0.0 1
diff --git a/ui/assets/human/buildstat/frame.tga b/ui/assets/human/buildstat/frame.tga
new file mode 100644
index 0000000..3b1e1f5
--- /dev/null
+++ b/ui/assets/human/buildstat/frame.tga
Binary files differ
diff --git a/ui/assets/human/buildstat/mark.tga b/ui/assets/human/buildstat/mark.tga
new file mode 100644
index 0000000..ef9123c
--- /dev/null
+++ b/ui/assets/human/buildstat/mark.tga
Binary files differ
diff --git a/ui/assets/human/buildstat/nopower.tga b/ui/assets/human/buildstat/nopower.tga
new file mode 100644
index 0000000..4d70384
--- /dev/null
+++ b/ui/assets/human/buildstat/nopower.tga
Binary files differ
diff --git a/ui/drop.menu b/ui/drop.menu
new file mode 100644
index 0000000..0db2264
--- /dev/null
+++ b/ui/drop.menu
@@ -0,0 +1,126 @@
+#include "ui/menudef.h"
+
+{
+ \\ ERROR \\
+
+ menuDef
+ {
+ name "drop_popmenu"
+ visible 0
+ fullscreen 0
+ rect 158 80 320 320
+ focusColor 1 .75 0 1
+ style 1
+ border 1
+ popup
+ onClose { uiScript clearError }
+ onOpen { }
+ onESC
+ {
+ play "sound/misc/menu1.wav";
+ close drop_popmenu;
+ open main
+ }
+
+
+ itemDef
+ {
+ name window
+ rect 10 15 300 320
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+
+ border WINDOW_BORDER_FULL
+ borderSize 1.0
+ borderColor 0.5 0.5 0.5 1
+ }
+
+ itemDef
+ {
+ name dropinfo
+ rect 0 50 320 20
+ text "Disconnected:"
+ textalign 1
+ textstyle 6
+ textscale .333
+ textalignx 160
+ textaligny 23
+ forecolor 1 1 1 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name dropinfo
+ rect 60 80 200 270
+ type ITEM_TYPE_TEXT
+ style 1
+ textstyle 3
+ autowrapped
+ cvar "com_errorMessage"
+ textalign ITEM_ALIGN_CENTER
+ textalignx 100
+ textaligny 23
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ decoration
+ }
+
+
+ // BUTTON //
+
+
+ itemDef
+ {
+ name exit
+ text "OK"
+ type 1
+ textscale .25
+ group grpControlbutton
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ rect 120 295 35 26
+ textalign 1
+ textalignx 22
+ textaligny 20
+ forecolor 1 1 1 1
+ backcolor .37 .1 .1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ close drop_popmenu;
+ open main
+ }
+ }
+
+ itemDef
+ {
+ name reconnect
+ text "Reconnect"
+ type 1
+ textscale .25
+ group grpControlbutton
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ rect 165 295 55 26
+ textalign 1
+ textalignx 22
+ textaligny 20
+ forecolor 1 1 1 1
+ backcolor .37 .1 .1 1
+ visible 1
+ action
+ {
+ close drop_popmenu;
+ exec "reconnect";
+ }
+ }
+ }
+}
+
+
diff --git a/ui/ingame.menu b/ui/ingame.menu
new file mode 100644
index 0000000..13bd1f3
--- /dev/null
+++ b/ui/ingame.menu
@@ -0,0 +1,115 @@
+#include "ui/menudef.h"
+{
+ assetGlobalDef
+ {
+ font "fonts/font" 26 // font
+ smallFont "fonts/smallfont" 20 // font
+ bigFont "fonts/bigfont" 34 // font
+ cursor "ui/assets/3_cursor3" // cursor
+ gradientBar "ui/assets/gradientbar2.tga" // gradient bar
+ itemFocusSound "sound/misc/menu2.wav" // sound for item getting focus (via keyboard or mouse )
+
+ fadeClamp 1.0 // sets the fadeup alpha
+ fadeCycle 1 // how often fade happens in milliseconds
+ fadeAmount 0.1 // amount to adjust alpha per cycle
+
+ shadowColor 0.1 0.1 0.1 0.25 // shadow color
+ }
+
+
+ \\ INGAME MENU \\
+
+ menuDef
+ {
+ name "ingame"
+ style WINDOW_STYLE_FILLED
+ visible 0
+ fullScreen 0
+ outOfBoundsClick // this closes the window if it gets a click out of the rectangle
+ rect 0 0 640 48
+ focusColor 1 .75 0 1
+ disableColor .5 .5 .5 1
+ backColor 0 0 0 1
+
+ onEsc
+ {
+ close ingame;
+ }
+
+ itemDef
+ {
+ name splashmodel
+ rect 0 -10 640 66
+ type ITEM_TYPE_MODEL
+ style WINDOW_STYLE_FILLED
+ asset_model "models/splash/splash_screen.md3"
+ model_fovx 32.0
+ model_fovy 3.8
+ model_angle 180
+ visible 1
+ decoration
+ backcolor 0 0 0 1
+ }
+
+ itemdef
+ {
+ name game
+ text "Game"
+ rect 35 6 65 40
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ textalign ITEM_ALIGN_CENTER
+ textalignx 32
+ textaligny 28
+ textscale .4
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open ingame_game
+ }
+ }
+
+ itemDef
+ {
+ name options
+ text "Options"
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ rect 100 6 70 40
+ textalign ITEM_ALIGN_CENTER
+ textalignx 35
+ textaligny 28
+ textscale .4
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open ingame_options
+ }
+ }
+
+ itemDef
+ {
+ name leave
+ text "Exit"
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ //rect 220 6 50 40
+ rect 170 6 50 40
+ textalign ITEM_ALIGN_CENTER
+ textalignx 25
+ textaligny 28
+ textscale .4
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open ingame_leave
+ }
+ }
+ }
+}
diff --git a/ui/ingame.txt b/ui/ingame.txt
new file mode 100644
index 0000000..185ce97
--- /dev/null
+++ b/ui/ingame.txt
@@ -0,0 +1,8 @@
+// menu defs
+//
+{
+ loadMenu { "ui/ingame.menu" }
+ loadMenu { "ui/ingame_game.menu" }
+ loadMenu { "ui/ingame_options.menu" }
+ loadMenu { "ui/ingame_leave.menu" }
+}
diff --git a/ui/ingame_game.menu b/ui/ingame_game.menu
new file mode 100644
index 0000000..0200ed9
--- /dev/null
+++ b/ui/ingame_game.menu
@@ -0,0 +1,3206 @@
+#include "ui/menudef.h"
+
+{
+ \\ INGAME GAME BOX \\
+
+ menuDef
+ {
+ name "ingame_game"
+ visible 0
+ fullscreen 0
+ outOfBoundsClick // this closes the window if it gets a click out of the rectangle
+ rect 10 56 292 280
+ focusColor 1 .75 0 1
+ onopen
+ {
+ uiScript InitIgnoreList;
+ uiScript loadArenas;
+ uiScript loadServerInfo;
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0
+ }
+
+ itemDef
+ {
+ name window
+ rect 10 5 292 270
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+
+ border WINDOW_BORDER_KCGRADIENT
+ borderSize 2.0
+ borderColor 0.5 0.5 0.5 1
+ }
+
+ //Section menus
+ itemDef
+ {
+ name voteBtn
+ text "Vote"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 35 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+ itemDef
+ {
+ name ignoreBtn
+ text "Ignore"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 100 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show ignore;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 0.2 0.2 0.2 1.0
+ }
+ }
+
+ itemDef
+ {
+ name infoBtn
+ text "Info"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 165 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show info;
+
+ setitemcolor infoBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+
+//////// INFO
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 55 256 20
+ type 4
+ style 0
+ text "Server Name:"
+ cvar ui_serverinfo_hostname
+ maxPaintChars 32
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 70 256 20
+ type 4
+ style 0
+ text "Time Limit:"
+ maxPaintChars 12
+ cvar ui_serverinfo_timelimit
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 85 256 20
+ type 4
+ style 0
+ text "Sudden Death Time:"
+ cvar ui_serverinfo_sd
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 100 256 20
+ type 4
+ style 0
+ text "Max Clients:"
+ cvar ui_serverinfo_maxclients
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 115 256 20
+ type 4
+ style 0
+ text "Map Name:"
+ cvar ui_serverinfo_mapname
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 130 256 20
+ type 11
+ style 0
+ text "Lag Correction:"
+ cvar ui_serverinfo_unlagged
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 145 256 20
+ type ITEM_TYPE_MULTI
+ style 0
+ text "Friendly Fire:"
+ cvarFloat ui_serverinfo_ff 0 0 7
+ cvarFloatList { "Off" 0 "Humans Only" 1 "Aliens Only" 2 "Both Teams" 3 "Buildables Only" 4 "Humans and Buildables" 5 "Aliens and Buildables" 6 "Both Teams and Buildables" 7 }
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 160 256 20
+ type 4
+ style 0
+ text "Version:"
+ cvar ui_serverinfo_version
+ maxPaintChars 45
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+//////// VOTE
+
+ //Vote menu
+ itemDef
+ {
+ name vote
+ text "Map"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 60 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show mapvote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Players"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 85 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show playervote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Team"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 110 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show teamvote;
+ show vote;
+ }
+ }
+
+///// Map Vote
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style 0
+ ownerdraw UI_STARTMAPCINEMATIC
+ rect 111 61 80 60
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style WINDOW_STYLE_FILLED
+ rect 110 60 82 62
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text ""
+ ownerdraw UI_ALLMAPS_SELECTION
+ textscale .225
+ rect 200 80 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ rect 110 122 150 85
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_ALLMAPS
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ doubleclick
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Load Selected Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 210 80 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Restart Current Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 230 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote map_restart";
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "End Match In Draw"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 250 110 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote draw";
+ uiScript closeingame
+ }
+ }
+
+///// Player Vote
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Selected Player:"
+ ownerdraw UI_PLAYERLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_PLAYER_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Kick Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMute;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Un-Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteUnMute;
+ uiScript closeingame
+ }
+ }
+
+
+///// Team Vote
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Selected Teammate:"
+ ownerdraw UI_TEAMLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_TEAM_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Kick Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Deny Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamDenyBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Allow Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamAllowBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Admit Defeat"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 235 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callteamvote admitdefeat";
+ uiScript closeingame
+ }
+ }
+
+//////// IGNORE
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 45 70 40 5
+ text "Player Name"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 190 70 40 5
+ text "Ignored"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 230 70 40 5
+ text "Ignoring You"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 35 75 240 130
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 16
+ textscale .225
+ border 1
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_IGNORE_LIST
+ visible 0
+ columns 3
+ 2 40 32 ITEM_ALIGN_LEFT
+ 150 15 1 ITEM_ALIGN_LEFT
+ 190 15 1 ITEM_ALIGN_LEFT
+ doubleClick {
+ play "sound/misc/menu1.wav";
+ uiScript ToggleIgnore
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Ignore Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 60 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript IgnorePlayer
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Stop Ignoring Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 190 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript UnIgnorePlayer
+ }
+ }
+
+ }
+}
+#include "ui/menudef.h"
+
+{
+ \\ INGAME GAME BOX \\
+
+ menuDef
+ {
+ name "ingame_game"
+ visible 0
+ fullscreen 0
+ outOfBoundsClick // this closes the window if it gets a click out of the rectangle
+ rect 10 56 292 280
+ focusColor 1 .75 0 1
+ onopen
+ {
+ uiScript InitIgnoreList;
+ uiScript loadArenas;
+ uiScript loadServerInfo;
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0
+ }
+
+ itemDef
+ {
+ name window
+ rect 10 5 292 270
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+
+ border WINDOW_BORDER_KCGRADIENT
+ borderSize 2.0
+ borderColor 0.5 0.5 0.5 1
+ }
+
+ //Section menus
+ itemDef
+ {
+ name voteBtn
+ text "Vote"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 35 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+ itemDef
+ {
+ name ignoreBtn
+ text "Ignore"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 100 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show ignore;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 0.2 0.2 0.2 1.0
+ }
+ }
+
+ itemDef
+ {
+ name infoBtn
+ text "Info"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 165 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show info;
+
+ setitemcolor infoBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+
+//////// INFO
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 55 256 20
+ type 4
+ style 0
+ text "Server Name:"
+ cvar ui_serverinfo_hostname
+ maxPaintChars 32
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 70 256 20
+ type 4
+ style 0
+ text "Time Limit:"
+ maxPaintChars 12
+ cvar ui_serverinfo_timelimit
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 85 256 20
+ type 4
+ style 0
+ text "Sudden Death Time:"
+ cvar ui_serverinfo_sd
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 100 256 20
+ type 4
+ style 0
+ text "Max Clients:"
+ cvar ui_serverinfo_maxclients
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 115 256 20
+ type 4
+ style 0
+ text "Map Name:"
+ cvar ui_serverinfo_mapname
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 130 256 20
+ type 11
+ style 0
+ text "Lag Correction:"
+ cvar ui_serverinfo_unlagged
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 145 256 20
+ type 11
+ style 0
+ text "Friendly Fire:"
+ cvar ui_serverinfo_ff
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 160 256 20
+ type 4
+ style 0
+ text "Version:"
+ cvar version
+ maxPaintChars 45
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+//////// VOTE
+
+ //Vote menu
+ itemDef
+ {
+ name vote
+ text "Map"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 60 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show mapvote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Players"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 85 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show playervote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Team"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 110 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show teamvote;
+ show vote;
+ }
+ }
+
+///// Map Vote
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style 0
+ ownerdraw UI_STARTMAPCINEMATIC
+ rect 111 61 80 60
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style WINDOW_STYLE_FILLED
+ rect 110 60 82 62
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text ""
+ ownerdraw UI_ALLMAPS_SELECTION
+ textscale .225
+ rect 200 80 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ rect 110 122 150 85
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_ALLMAPS
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ doubleclick
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Load Selected Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 210 80 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Restart Current Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 230 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote map_restart";
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "End Match In Draw"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 250 110 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote draw";
+ uiScript closeingame
+ }
+ }
+
+///// Player Vote
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Selected Player:"
+ ownerdraw UI_PLAYERLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_PLAYER_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Kick Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMute;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Un-Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteUnMute;
+ uiScript closeingame
+ }
+ }
+
+
+///// Team Vote
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Selected Teammate:"
+ ownerdraw UI_TEAMLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_TEAM_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Kick Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Deny Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamDenyBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Allow Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamAllowBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Admit Defeat"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 235 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callteamvote admitdefeat";
+ uiScript closeingame
+ }
+ }
+
+//////// IGNORE
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 45 70 40 5
+ text "Player Name"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 190 70 40 5
+ text "Ignored"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 230 70 40 5
+ text "Ignoring You"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 35 75 240 130
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 16
+ textscale .225
+ border 1
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_IGNORE_LIST
+ visible 0
+ columns 3
+ 2 40 32 ITEM_ALIGN_LEFT
+ 150 15 1 ITEM_ALIGN_LEFT
+ 190 15 1 ITEM_ALIGN_LEFT
+ doubleClick {
+ play "sound/misc/menu1.wav";
+ uiScript ToggleIgnore
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Ignore Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 60 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript IgnorePlayer
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Stop Ignoring Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 190 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript UnIgnorePlayer
+ }
+ }
+
+ }
+}
+#include "ui/menudef.h"
+
+{
+ \\ INGAME GAME BOX \\
+
+ menuDef
+ {
+ name "ingame_game"
+ visible 0
+ fullscreen 0
+ outOfBoundsClick // this closes the window if it gets a click out of the rectangle
+ rect 10 56 292 280
+ focusColor 1 .75 0 1
+ onopen
+ {
+ uiScript InitIgnoreList;
+ uiScript loadArenas;
+ uiScript loadServerInfo;
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0
+ }
+
+ itemDef
+ {
+ name window
+ rect 10 5 292 270
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+
+ border WINDOW_BORDER_KCGRADIENT
+ borderSize 2.0
+ borderColor 0.5 0.5 0.5 1
+ }
+
+ //Section menus
+ itemDef
+ {
+ name voteBtn
+ text "Vote"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 35 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+ itemDef
+ {
+ name ignoreBtn
+ text "Ignore"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 100 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show ignore;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 0.2 0.2 0.2 1.0
+ }
+ }
+
+ itemDef
+ {
+ name infoBtn
+ text "Info"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 165 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show info;
+
+ setitemcolor infoBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+
+//////// INFO
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 55 256 20
+ type 4
+ style 0
+ text "Server Name:"
+ cvar ui_serverinfo_hostname
+ maxPaintChars 32
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 70 256 20
+ type 4
+ style 0
+ text "Time Limit:"
+ maxPaintChars 12
+ cvar ui_serverinfo_timelimit
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 85 256 20
+ type 4
+ style 0
+ text "Sudden Death Time:"
+ cvar ui_serverinfo_sd
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 100 256 20
+ type 4
+ style 0
+ text "Max Clients:"
+ cvar ui_serverinfo_maxclients
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 115 256 20
+ type 4
+ style 0
+ text "Map Name:"
+ cvar ui_serverinfo_mapname
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 130 256 20
+ type 11
+ style 0
+ text "Lag Correction:"
+ cvar ui_serverinfo_unlagged
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 145 256 20
+ type 11
+ style 0
+ text "Friendly Fire:"
+ cvar ui_serverinfo_ff
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 160 256 20
+ type 4
+ style 0
+ text "Version:"
+ cvar version
+ maxPaintChars 45
+ textalign ITEM_ALIGN_RIGHT
+ textaligny 12
+ textalignx 75
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+//////// VOTE
+
+ //Vote menu
+ itemDef
+ {
+ name vote
+ text "Map"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 60 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show mapvote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Players"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 85 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show playervote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Team"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 110 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show teamvote;
+ show vote;
+ }
+ }
+
+///// Map Vote
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style 0
+ ownerdraw UI_STARTMAPCINEMATIC
+ rect 111 61 80 60
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style WINDOW_STYLE_FILLED
+ rect 110 60 82 62
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text ""
+ ownerdraw UI_ALLMAPS_SELECTION
+ textscale .225
+ rect 200 80 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ rect 110 122 150 85
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_ALLMAPS
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ doubleclick
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Load Selected Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 210 80 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Restart Current Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 230 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote map_restart";
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "End Match In Draw"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 250 110 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote draw";
+ uiScript closeingame
+ }
+ }
+
+///// Player Vote
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Selected Player:"
+ ownerdraw UI_PLAYERLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_PLAYER_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Kick Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMute;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Un-Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteUnMute;
+ uiScript closeingame
+ }
+ }
+
+
+///// Team Vote
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Selected Teammate:"
+ ownerdraw UI_TEAMLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_TEAM_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Kick Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Deny Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamDenyBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Allow Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamAllowBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Admit Defeat"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 235 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callteamvote admitdefeat";
+ uiScript closeingame
+ }
+ }
+
+//////// IGNORE
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 45 70 40 5
+ text "Player Name"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 190 70 40 5
+ text "Ignored"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 230 70 40 5
+ text "Ignoring You"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 35 75 240 130
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 16
+ textscale .225
+ border 1
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_IGNORE_LIST
+ visible 0
+ columns 3
+ 2 40 32 ITEM_ALIGN_LEFT
+ 150 15 1 ITEM_ALIGN_LEFT
+ 190 15 1 ITEM_ALIGN_LEFT
+ doubleClick {
+ play "sound/misc/menu1.wav";
+ uiScript ToggleIgnore
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Ignore Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 60 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript IgnorePlayer
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Stop Ignoring Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 190 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript UnIgnorePlayer
+ }
+ }
+
+ }
+}
+#include "ui/menudef.h"
+
+{
+ \\ INGAME GAME BOX \\
+
+ menuDef
+ {
+ name "ingame_game"
+ visible 0
+ fullscreen 0
+ outOfBoundsClick // this closes the window if it gets a click out of the rectangle
+ rect 10 56 292 280
+ focusColor 1 .75 0 1
+ onopen
+ {
+ uiScript InitIgnoreList;
+ uiScript loadArenas;
+ uiScript loadServerInfo;
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0
+ }
+
+ itemDef
+ {
+ name window
+ rect 10 5 292 270
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+
+ border WINDOW_BORDER_KCGRADIENT
+ borderSize 2.0
+ borderColor 0.5 0.5 0.5 1
+ }
+
+ //Section menus
+ itemDef
+ {
+ name voteBtn
+ text "Vote"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 35 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show vote;
+ show mapvote;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+ itemDef
+ {
+ name ignoreBtn
+ text "Ignore"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 100 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show ignore;
+
+ setitemcolor infoBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 0.2 0.2 0.2 1.0
+ }
+ }
+
+ itemDef
+ {
+ name infoBtn
+ text "Info"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 165 22 40 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show info;
+
+ setitemcolor infoBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor voteBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor ignoreBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+
+//////// ABOUT
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 55 256 20
+ type 4
+ style 0
+ text "Server Name:"
+ cvar ui_serverinfo_hostname
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 70 256 20
+ type 4
+ style 0
+ text "Time Limit:"
+ maxPaintChars 12
+ cvar ui_serverinfo_timelimit
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 85 256 20
+ type 4
+ style 0
+ text "Sudden Death Time:"
+ cvar ui_serverinfo_sd
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 100 256 20
+ type 4
+ style 0
+ text "Max Clients:"
+ cvar ui_serverinfo_maxclients
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 115 256 20
+ type 4
+ style 0
+ text "Map Name:"
+ cvar ui_serverinfo_mapname
+ maxPaintChars 12
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 130 256 20
+ type 11
+ style 0
+ text "Lag Correction:"
+ cvar ui_serverinfo_unlagged
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 145 256 20
+ type 11
+ style 0
+ text "Friendly Fire:"
+ cvar ui_serverinfo_ff
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name info
+ group gameGrp
+ rect 30 160 256 20
+ type 4
+ style 0
+ text "Version:"
+ cvar version
+ maxPaintChars 45
+ textalign ITEM_ALIGN_LEFT
+ textalignx 128
+ textaligny 12
+ textalign 1
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+//////// VOTE
+
+ //Vote menu
+ itemDef
+ {
+ name vote
+ text "Map"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 60 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show mapvote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Players"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 85 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show playervote;
+ show vote;
+ }
+ }
+
+ itemDef
+ {
+ name vote
+ text "Team"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 110 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide gameGrp;
+ show teamvote;
+ show vote;
+ }
+ }
+
+///// Map Vote
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style 0
+ ownerdraw UI_STARTMAPCINEMATIC
+ rect 111 61 80 60
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ style WINDOW_STYLE_FILLED
+ rect 110 60 82 62
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text ""
+ ownerdraw UI_ALLMAPS_SELECTION
+ textscale .225
+ rect 200 80 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ rect 110 122 150 85
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_ALLMAPS
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ doubleclick
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Load Selected Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 210 80 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMap;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "Restart Current Map"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 230 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote map_restart";
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name mapvote
+ group gameGrp
+ text "End Match In Draw"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 250 110 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callvote draw";
+ uiScript closeingame
+ }
+ }
+
+///// Player Vote
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Selected Player:"
+ ownerdraw UI_PLAYERLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_PLAYER_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Kick Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 90 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteMute;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name playervote
+ group gameGrp
+ text "Un-Mute Selected Player"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteUnMute;
+ uiScript closeingame
+ }
+ }
+
+
+///// Team Vote
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Selected Teammate:"
+ ownerdraw UI_TEAMLIST_SELECTION
+ textscale .225
+ rect 110 60 110 20
+ textalign 0
+ textalignx 0
+ textaligny 16
+ forecolor 1 1 1 1
+ decoration
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ rect 110 80 170 85
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_LISTBOX
+ elementwidth 120
+ elementheight 15
+ textscale .225
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_TEAM_LIST
+ border 1
+ bordercolor 0.5 0.5 0.5 0.5
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 0
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Kick Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 175 100 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamKick;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Deny Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 195 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamDenyBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Allow Building For Selected Teammate"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 215 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript voteTeamAllowBuild;
+ uiScript closeingame
+ }
+ }
+
+ itemDef
+ {
+ name teamvote
+ group gameGrp
+ text "Admit Defeat"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ rect 110 235 150 20
+ textalign ITEM_ALIGN_LEFT
+ textalignx 5
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "cmd callteamvote admitdefeat";
+ uiScript closeingame
+ }
+ }
+
+//////// IGNORE
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 45 70 40 5
+ text "Player Name"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 190 70 40 5
+ text "Ignored"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 230 70 40 5
+ text "Ignoring You"
+ visible 0
+ type ITEM_TYPE_TEXT
+ textscale .225
+ }
+ itemDef
+ {
+ name ignore
+ group gameGrp
+ rect 35 75 240 130
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 16
+ textscale .225
+ border 1
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_IGNORE_LIST
+ visible 0
+ columns 3
+ 2 40 32 ITEM_ALIGN_LEFT
+ 150 15 1 ITEM_ALIGN_LEFT
+ 190 15 1 ITEM_ALIGN_LEFT
+ doubleClick {
+ play "sound/misc/menu1.wav";
+ uiScript ToggleIgnore
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Ignore Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 60 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript IgnorePlayer
+ }
+ }
+
+ itemDef
+ {
+ name ignore
+ text "Stop Ignoring Player"
+ group gameGrp
+ style WINDOW_STYLE_EMPTY
+ rect 190 210 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript UnIgnorePlayer
+ }
+ }
+
+ }
+}
diff --git a/ui/ingame_options.menu b/ui/ingame_options.menu
new file mode 100644
index 0000000..e0f08aa
--- /dev/null
+++ b/ui/ingame_options.menu
@@ -0,0 +1,2180 @@
+#include "ui/menudef.h"
+
+{
+ \\ INGAME OPTIONS BOX \\
+
+ menuDef
+ {
+ name "ingame_options"
+ visible 0
+ fullscreen 0
+ outOfBoundsClick // this closes the window if it gets a click out of the rectangle
+ rect 10 56 292 280
+ focusColor 1 .75 0 1
+ onopen
+ {
+ hide optionsGrp;
+ show game;
+
+ setitemcolor gameBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor controlsBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor systemBtn forecolor 1.0 1.0 1.0 1.0
+ }
+
+ itemDef
+ {
+ name window
+ rect 10 5 292 270
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+
+ border WINDOW_BORDER_KCGRADIENT
+ borderSize 2.0
+ borderColor 0.5 0.5 0.5 1
+ }
+
+ //Section menus
+ itemDef
+ {
+ name GameBtn
+ text "Game"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 80 20 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_CENTER
+ textalignx 34
+ textaligny 18
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show game;
+
+ setitemcolor gameBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor controlsBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor systemBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+ itemDef
+ {
+ name controlsBtn
+ text "Controls"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 160 20 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_CENTER
+ textalignx 34
+ textaligny 18
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show controls;
+ show look;
+
+ setitemcolor gameBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor controlsBtn forecolor 0.2 0.2 0.2 1.0;
+ setitemcolor systemBtn forecolor 1.0 1.0 1.0 1.0
+ }
+ }
+
+ itemDef
+ {
+ name systemBtn
+ text "System"
+ group menuGrp
+ style WINDOW_STYLE_EMPTY
+ rect 230 20 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_CENTER
+ textalignx 34
+ textaligny 18
+ textscale .35
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show system;
+ show ghardware;
+
+ setitemcolor gameBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor controlsBtn forecolor 1.0 1.0 1.0 1.0;
+ setitemcolor systemBtn forecolor 0.2 0.2 0.2 1.0
+ }
+ }
+
+//////// GAME
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_EDITFIELD
+ style 0
+ text "Name:"
+ cvar "name"
+ maxchars 31
+ maxPaintChars 31
+ rect 50 85 220 15
+ textalign ITEM_ALIGN_LEFT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Auto Download:"
+ cvar "cl_allowDownload"
+ rect 80 115 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Taunts Sounds Off:"
+ cvar "cg_noTaunt"
+ rect 80 130 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Team Chats Only:"
+ cvar "cg_teamChatsOnly"
+ rect 80 145 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Auto Wallwalk Pitching:"
+ cvar "cg_wwFollow"
+ rect 80 160 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Wallwalking Speed:"
+ cvarfloat "cg_wwSmoothTime" 300 0 1000
+ cvarFloatList { "Medium" 300 "Fast" 150 "Instant" 0 "Slow" 600 }
+ rect 80 175 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Wallwalk Control Toggles:"
+ cvar "cg_wwToggle"
+ rect 80 190 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Disable Warning Dialogs:"
+ cvar "cg_disableWarningDialogs"
+ rect 80 205 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Tutorial Mode:"
+ cvar "cg_tutorial"
+ rect 80 220 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Show Clock:"
+ cvar "cg_drawClock"
+ cvarFloatList { "No" 0 "12 Hour" 1 "24 Hour" 2 }
+ rect 80 235 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Draw Crosshair:"
+ cvar "cg_drawCrosshair"
+ cvarFloatList { "Never" 0 "Ranged Weapons Only" 1 "Always" 2 }
+ rect 80 250 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+//////// CONTROLS
+
+ //Controls menu
+ itemDef
+ {
+ name controls
+ text "Look"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 60 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show controls;
+ show look
+ }
+ }
+
+//////// LOOK
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Lookup:"
+ cvar "+lookup"
+ rect 96 85 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Look Down:"
+ cvar "+lookdown"
+ rect 96 100 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Mouse Look:"
+ cvar "+mlook"
+ rect 96 115 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Centerview:"
+ cvar "centerview"
+ rect 96 130 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Free Look:"
+ cvar "cl_freelook"
+ rect 96 145 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_SLIDER
+ text "Mouse Sensitivity:"
+ cvarfloat "sensitivity" 5 1 30
+ rect 96 160 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 15
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ }
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Invert Mouse:"
+ cvar "ui_mousePitch"
+ rect 96 180 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript update ui_mousePitch
+ }
+ }
+
+ itemDef
+ {
+ name look
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Smooth Mouse:"
+ cvar "m_filter"
+ rect 96 195 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+//////// MOVE
+
+ itemDef
+ {
+ name controls
+ text "Move"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 80 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show controls;
+ show move
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Always Run:"
+ cvar "cl_run"
+ rect 96 65 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Run / Walk:"
+ cvar "+speed"
+ rect 96 80 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Sprint:"
+ cvar "boost"
+ rect 96 95 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Forward:"
+ cvar "+forward"
+ rect 96 110 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Backpedal:"
+ cvar "+back"
+ rect 96 125 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Move Left:"
+ cvar "+moveleft"
+ rect 96 140 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Move Right:"
+ cvar "+moveright"
+ rect 96 155 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Jump:"
+ cvar "+moveup"
+ rect 96 170 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Crouch:"
+ cvar "+movedown"
+ rect 96 185 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Turn Left:"
+ cvar "+left"
+ rect 96 200 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Turn Right:"
+ cvar "+right"
+ rect 96 215 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name move
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Strafe:"
+ cvar "+strafe"
+ rect 96 230 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+
+
+//////// UPGRADES
+
+ itemDef
+ {
+ name controls
+ text "Upgrades"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 100 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show controls;
+ show upgrades
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Primary Attack:"
+ cvar "+attack"
+ rect 96 90 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Secondary Attack:"
+ cvar "+button5"
+ rect 96 105 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Previous Upgrade:"
+ cvar "weapprev"
+ rect 96 120 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Next Upgrade:"
+ cvar "weapnext"
+ rect 96 135 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Activate Upgrade:"
+ cvar "+button2"
+ rect 96 150 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Reload:"
+ cvar "reload"
+ rect 96 165 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Buy Ammo:"
+ cvar "buy ammo"
+ rect 96 180 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name upgrades
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Use Medkit:"
+ cvar "itemact medkit"
+ rect 96 195 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+//////// MISC
+
+ itemDef
+ {
+ name controls
+ text "Misc"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 120 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show controls;
+ show misc
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Show Scores:"
+ cvar "+scores"
+ rect 96 65 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Scroll Scores Up:"
+ cvar "scoresUp"
+ rect 96 80 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Scroll Scores Down:"
+ cvar "scoresDown"
+ rect 96 95 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Use Structure/Evolve:"
+ cvar "+button7"
+ rect 96 110 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Deconstruct Structure:"
+ cvar "deconstruct"
+ rect 96 125 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Gesture:"
+ cvar "+button3"
+ rect 96 140 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Chat:"
+ cvar "messagemode"
+ rect 96 155 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Team Chat:"
+ cvar "messagemode2"
+ rect 96 170 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Target Chat:"
+ cvar "messagemode3"
+ rect 96 185 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Attack Chat:"
+ cvar "messagemode4"
+ rect 96 200 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Vote Yes:"
+ cvar "vote yes"
+ rect 96 215 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Vote No:"
+ cvar "vote no"
+ rect 96 230 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Team Vote Yes:"
+ cvar "teamvote yes"
+ rect 96 245 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name misc
+ group optionsGrp
+ type ITEM_TYPE_BIND
+ text "Team Vote No:"
+ cvar "teamvote no"
+ rect 96 260 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ maxPaintChars 20
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+
+
+//////// SYSTEM
+
+ //System menu
+ itemDef
+ {
+ name system
+ text "GFX Hardware"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 60 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show system;
+ show ghardware
+ }
+ }
+
+//////// GFX HARDWARE
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Quality:"
+ cvar "ui_glCustom"
+ cvarFloatList { "High Quality" 0 "Normal" 1 "Fast" 2 "Fastest" 3 "Custom" 4 }
+ rect 96 50 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript update "ui_glCustom"
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_EDITFIELD
+ text "GL Driver:"
+ cvar "r_gldriver"
+ //cvarFloatList { }
+ rect 96 65 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "GL Extensions:"
+ cvar "r_allowExtensions"
+ rect 96 80 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCuston
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Video Mode:"
+ cvar "r_mode"
+ cvarFloatList { "320x240" 0 "400x300" 1 "512x384" 2 "640x480" 3
+ "800x600" 4 "960x720" 5 "1024x768" 6 "1152x864" 7
+ "1280x1024" 8 "1600x1200" 9 "2048x1536" 10 "856x480 wide screen" 11 }
+ rect 96 95 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Color Depth:"
+ cvar "r_colorbits"
+ cvarFloatList { "Default" 0 "16 bit" 16 "32 bit" 32 }
+ rect 96 110 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom;
+ uiScript update "r_colorbits"
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Fullscreen:"
+ cvar "r_fullscreen"
+ rect 96 125 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Lighting:"
+ cvar "r_vertexlight"
+ cvarFloatList { "Light Map (high)" 0 "Vertex (low)" 1 }
+ rect 96 140 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Geometric Detail:"
+ cvar "r_lodbias"
+ cvarFloatList { "High" 0 "Medium" 1 "Low" 2 }
+ rect 96 155 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom;
+ uiScript update "r_lodbias"
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Texture Detail:"
+ cvar "r_picmip"
+ cvarFloatList { "Low" 2 "Normal" 1 "High" 0 }
+ rect 96 170 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Texture Quality:"
+ cvar "r_texturebits"
+ cvarFloatList { "Default" 0 "16 bit" 16 "32 bit" 32 }
+ rect 96 185 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Texture Filter:"
+ cvar "r_texturemode"
+ cvarStrList { "Bilinear", "GL_LINEAR_MIPMAP_NEAREST", "Trilinear", "GL_LINEAR_MIPMAP_LINEAR" }
+ rect 96 200 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Anisotropic Filtering:"
+ cvar "r_ext_texture_filter_anisotropic"
+ rect 96 215 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Compress Textures:"
+ cvar "r_ext_compressed_textures "
+ rect 96 230 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ name ghardware
+ group optionsGrp
+ type ITEM_TYPE_BUTTON
+ text "APPLY"
+ textscale .25
+ style WINDOW_STYLE_EMPTY
+ rect 144 245 75 20
+ textalign ITEM_ALIGN_CENTER
+ textalignx 37
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "vid_restart"
+ }
+ }
+
+//////// GFX SOFTWARE
+
+ itemDef
+ {
+ name system
+ text "GFX Software"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 80 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show system;
+ show gsoftware
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_SLIDER
+ text "Brightness:"
+ cvarfloat "r_gamma" 1 .5 2
+ rect 96 60 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 80
+ textaligny 17
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_SLIDER
+ text "Screen Size:"
+ cvarfloat "cg_viewsize" 100 30 100
+ //cvarFloatList { }
+ rect 96 80 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 80
+ textaligny 17
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Simple Items:"
+ cvar "cg_simpleItems"
+ rect 96 100 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Marks On Walls:"
+ cvar "cg_marks"
+ rect 96 115 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Dynamic Lights:"
+ cvar "r_dynamiclight"
+ rect 96 130 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Draw Gun:"
+ cvar "cg_drawGun"
+ rect 96 145 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Low Quality Sky:"
+ cvar "r_fastsky"
+ rect 96 160 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Sync Every Frame:"
+ cvar "weapon 5"
+ rect 96 175 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Show Time:"
+ cvar "cg_drawTimer"
+ rect 96 190 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "In Game Videos:"
+ cvar "r_inGameVideo"
+ rect 96 205 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Depth Sort Particles:"
+ cvar "cg_depthSortParticles"
+ rect 96 220 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Particle Physics:"
+ cvar "cg_bounceParticles"
+ cvarFloatList { "Low Quality" 0 "High Quality" 1 }
+ rect 96 235 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name gsoftware
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Light Flares:"
+ cvar "cg_lightFlare"
+ cvarFloatList { "Off" 0 "No Fade" 1 "Timed Fade" 2 "Real Fade" 3 }
+ rect 96 250 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+//////// GL INFO
+
+ itemDef
+ {
+ name system
+ text "GL Info"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 100 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show system;
+ show glinfo
+ }
+ }
+
+ itemDef
+ {
+ name glinfo
+ group optionsGrp
+ rect 104 35 230 230
+ ownerdraw UI_GLINFO
+ textalign 1
+ textscale .15
+ textalignx 0
+ textaligny 17
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ }
+
+//////// NET & SOUND
+
+ itemDef
+ {
+ name system
+ text "Net & Sound"
+ group optionsGrp
+ style WINDOW_STYLE_EMPTY
+ rect 20 120 64 20
+ type ITEM_TYPE_BUTTON
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 16
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ hide optionsGrp;
+ show system;
+ show netsound
+ }
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ style 1
+ text "Sound"
+ rect 96 50 192 20
+ textalign ITEM_ALIGN_CENTER
+ textalignx 80
+ textaligny 17
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ type ITEM_TYPE_SLIDER
+ text "Effects Volume:"
+ cvarfloat "s_volume" 0.7 0 1
+ rect 96 70 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ textaligny 17
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ type ITEM_TYPE_SLIDER
+ text "Music Volume:"
+ cvarfloat "s_musicvolume" 0.25 0 1
+ rect 96 90 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 90
+ textaligny 17
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "OpenAL:"
+ cvar "s_useOpenAL"
+ rect 96 120 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Sound Quality:"
+ cvar "s_khz"
+ cvarFloatList { "44 khz (very high)" 44 "22 khz (high)" 22 "11 khz (low)" 11 }
+ rect 96 135 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Doppler Sound:"
+ cvar "s_doppler"
+ rect 96 150 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ type ITEM_TYPE_BUTTON
+ text "APPLY"
+ textscale .25
+ style WINDOW_STYLE_EMPTY
+ rect 155 170 75 20
+ textalign ITEM_ALIGN_CENTER
+ textalignx 37
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "snd_restart"
+ }
+ }
+
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ style 1
+ text "Network"
+ rect 96 200 192 20
+ textalign ITEM_ALIGN_CENTER
+ textalignx 80
+ textaligny 17
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ decoration
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ name netsound
+ group optionsGrp
+ type ITEM_TYPE_MULTI
+ text "Net Data Rate:"
+ cvar "rate"
+ cvarFloatList { "<=28.8k" 2500 "33.6k" 3000 "56k" 4000 "ISDN" 5000 "LAN/CABLE/xDSl" 25000 }
+ rect 96 220 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 100
+ textaligny 17
+ textscale .25
+ forecolor 1 1 1 1
+ visible 0
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+ }
+}
diff --git a/ui/joinserver.menu b/ui/joinserver.menu
new file mode 100644
index 0000000..35439e1
--- /dev/null
+++ b/ui/joinserver.menu
@@ -0,0 +1,687 @@
+#include "ui/menudef.h"
+
+{
+
+ \\ Server Join \\
+
+ menuDef
+ {
+ name "joinserver"
+ visible 0
+ fullscreen 1
+ rect 0 0 640 480
+ focusColor 1 .75 0 1
+ outOfBoundsClick
+ style 0
+ onOpen
+ {
+ uiScript InitServerList 3;
+ hide accept_alt;
+ show accept;
+ hide back_alt;
+ show back;
+ hide grpmessage;
+ uiScript UpdateFilter
+ }
+
+ onEsc { uiScript closeJoin }
+
+ itemDef
+ {
+ name background
+ rect 0 0 640 480
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+ }
+
+ // DATE AND MESSAGE OF THE DAY //
+
+ itemDef
+ {
+ name datewindow
+ rect 10 365 265 25
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ backcolor 0 0 0 .15
+ visible 1
+ }
+
+ itemDef
+ {
+ name messagewindow
+ rect 275 365 355 25
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ backcolor 0 0 0 .15
+ visible 1
+ }
+
+ itemDef
+ {
+ name refreshdate
+ ownerdraw UI_SERVERREFRESHDATE
+ textscale .33
+ rect 10 365 265 25
+ textalign 0
+ textalignx 10
+ textaligny 20
+ forecolor 1 1 1 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name messageoftheday
+ ownerdraw UI_SERVERMOTD
+ textscale .33
+ rect 280 365 345 25
+ forecolor 1 1 1 1
+ visible 1
+ decoration
+ }
+
+
+ // VIEW OPTIONS //
+
+ itemDef
+ {
+ name gametypefield
+ style WINDOW_STYLE_EMPTY
+ ownerdraw UI_NETSOURCE
+ rect 26 20 128 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 22
+ textscale .4
+ forecolor 1 1 1 1
+ backcolor .5 .5 .5 .5
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav"
+ }
+ }
+
+ // BUTTONS //
+
+ itemDef
+ {
+ name refreshSource
+ text "Get New List"
+ type ITEM_TYPE_BUTTON
+ textscale .4
+ style WINDOW_STYLE_EMPTY
+ rect 190 20 128 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 22
+ backcolor .5 .5 .5 .5
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript RefreshServers
+ }
+ }
+
+ itemDef
+ {
+ name refreshFilter
+ text "Refresh List"
+ textscale .4
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_BUTTON
+ rect 354 20 128 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 22
+ backcolor .5 .5 .5 .5
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript RefreshFilter
+ }
+ }
+
+ itemDef
+ {
+ name viewEmpty
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_YESNO
+ text "View Empty:"
+ cvar "ui_browserShowEmpty"
+ textscale .4
+ rect 26 50 128 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 22
+ forecolor 1 1 1 1
+ backcolor .5 .5 .5 .5
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript RefreshFilter
+ }
+ }
+
+ itemDef
+ {
+ name viewFull
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_YESNO
+ text "View Full:"
+ cvar "ui_browserShowFull"
+ textscale .4
+ rect 190 50 128 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 22
+ forecolor 1 1 1 1
+ backcolor .5 .5 .5 .5
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript RefreshFilter
+ }
+ }
+
+
+ // map selection
+
+ itemDef
+ {
+ name mappreview
+ style 0
+ ownerdraw UI_NETMAPCINEMATIC
+ rect 502 5 128 96
+ border 1
+ bordercolor 0 .5 0 .5
+ visible 1
+ }
+
+ itemDef
+ {
+ name mappreview
+ style WINDOW_STYLE_FILLED
+ rect 502 5 128 96
+ border 1
+ bordercolor .5 .5 .5 .5
+ visible 1
+ }
+
+ // COLUMNS //
+
+ itemDef
+ {
+ name serverColumn
+ group grpColumn
+ rect 10 130 365 232
+ style WINDOW_STYLE_FILLED
+ border 1
+ backcolor 0 0 0 0
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name mapColumn
+ group grpColumn
+ rect 375 130 125 232
+ style WINDOW_STYLE_FILLED
+ border 1
+ backcolor 0 0 0 0
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name playerColumn
+ group grpColumn
+ rect 500 130 60 232
+ style WINDOW_STYLE_FILLED
+ border 1
+ backcolor 0 0 0 0
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name pingColumn
+ group grpColumn
+ rect 560 130 52 232
+ style WINDOW_STYLE_FILLED
+ border 1
+ backcolor 0 0 0 0
+ bordersize 1
+ bordercolor .5 .5 .5 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name serverlist
+ rect 10 130 620 232
+ type ITEM_TYPE_LISTBOX
+ style WINDOW_STYLE_EMPTY
+ elementwidth 120
+ elementheight 20
+ textscale .33
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_SERVERS
+ border 1
+ bordercolor 0.5 0.5 0.5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 1
+ columns 4
+ 2 40 80 ITEM_ALIGN_LEFT
+ 375 40 20 ITEM_ALIGN_LEFT
+ 500 5 10 ITEM_ALIGN_LEFT
+ 560 20 20 ITEM_ALIGN_LEFT
+
+ doubleClick { uiScript JoinServer }
+ }
+
+
+ // SORT TABS //
+
+ itemDef
+ {
+ name server
+ group grpTabs
+ text "Server Name"
+ type ITEM_TYPE_BUTTON
+ textscale .33
+ style WINDOW_STYLE_EMPTY
+ rect 10 103 365 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 18
+ border 1
+ bordercolor 0.5 0.5 0.5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript ServerSort 0;
+
+ setitemcolor grpColumn backcolor 0 0 0 0;
+ setitemcolor serverColumn backcolor 0.3 1 1 0.5
+ }
+ }
+
+ itemDef
+ {
+ name map
+ group grpTabs
+ type ITEM_TYPE_BUTTON
+ text "Map Name"
+ textscale .33
+ style WINDOW_STYLE_EMPTY
+ rect 375 103 125 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 18
+ border 1
+ bordercolor 0.5 0.5 0.5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript ServerSort 1;
+
+ setitemcolor grpColumn backcolor 0 0 0 0;
+ setitemcolor mapColumn backcolor 0.3 1 1 0.5
+ }
+ }
+
+ itemDef
+ {
+ name Players
+ group grpTabs
+ text "Players"
+ type ITEM_TYPE_BUTTON
+ textscale .33
+ style WINDOW_STYLE_EMPTY
+ rect 500 103 60 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 18
+ border 1
+ bordercolor 0.5 0.5 0.5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript ServerSort 2;
+
+ setitemcolor grpColumn backcolor 0 0 0 0;
+ setitemcolor playerColumn backcolor 0.3 1 1 0.5
+ }
+ }
+
+
+ itemDef
+ {
+ name Ping
+ group grpTabs
+ text "Ping"
+ type ITEM_TYPE_BUTTON
+ textscale .33
+ style WINDOW_STYLE_EMPTY
+ rect 560 103 70 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 18
+ border 1
+ bordercolor 0.5 0.5 0.5 1
+ forecolor 1 1 1 1
+ backcolor 0.2 0.2 0.2 1
+ outlinecolor 0.1 0.1 0.1 0.5
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript ServerSort 3;
+
+ setitemcolor grpColumn backcolor 0 0 0 0;
+ setitemcolor pingColumn backcolor 0.3 1 1 0.5
+ }
+ }
+
+
+ itemDef
+ {
+ name password
+ text "Password"
+ type ITEM_TYPE_BUTTON
+ textscale .4
+ style WINDOW_STYLE_FILLED
+ rect 20 395 92 26
+ textalign 1
+ textalignx 46 // center
+ textaligny 22
+ backcolor 0 0 0 1
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open password_popmenu
+ }
+ }
+
+ itemDef
+ {
+ name createFavorite
+ text "New Favorite"
+ type ITEM_TYPE_BUTTON
+ textscale .4
+ style WINDOW_STYLE_FILLED
+ rect 148 395 92 26
+ textalign 1
+ textalignx 46 // center
+ textaligny 22
+ backcolor 0 0 0 1
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open createfavorite_popmenu
+ }
+ }
+
+ itemDef
+ {
+ name addFavorite
+ text "Add Favorite"
+ type ITEM_TYPE_BUTTON
+ textscale .4
+ style WINDOW_STYLE_FILLED
+ ownerdrawFlag UI_SHOW_NOTFAVORITESERVERS
+ rect 276 395 92 26
+ textalign 1
+ textalignx 46 // center
+ textaligny 22
+ backcolor 0 0 0 1
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript addFavorite
+ }
+ }
+
+ itemDef
+ {
+ name delfavorite
+ text "Del. Favorite"
+ type ITEM_TYPE_BUTTON
+ textscale .4
+ style WINDOW_STYLE_FILLED
+ ownerdrawFlag UI_SHOW_FAVORITESERVERS
+ rect 276 395 92 26
+ textalign 1
+ textalignx 46 // center
+ textaligny 22
+ backcolor 0 0 0 1
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript DeleteFavorite;
+ uiScript UpdateFilter
+ }
+ }
+
+ itemDef
+ {
+ name serverinfo
+ text "Server Info"
+ type ITEM_TYPE_BUTTON
+ textscale .4
+ style WINDOW_STYLE_FILLED
+ rect 404 395 92 26
+ textalign 1
+ textalignx 46 // center
+ textaligny 22
+ backcolor 0 0 0 1
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open serverinfo_popmenu
+ }
+ }
+
+ itemDef
+ {
+ name findplayer
+ text "Find Friend"
+ type ITEM_TYPE_BUTTON
+ textscale .4
+ style WINDOW_STYLE_FILLED
+ rect 532 395 92 26
+ textalign 1
+ textalignx 46 // center
+ textaligny 22
+ backcolor 0 0 0 1
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open findplayer_popmenu
+ }
+ }
+
+
+
+ itemDef
+ {
+ name createServer
+ text "Create Server"
+ textscale .5
+ style WINDOW_STYLE_EMPTY
+ type ITEM_TYPE_BUTTON
+ rect 254 436 128 26
+ textalign ITEM_ALIGN_LEFT
+ textalignx 10
+ textaligny 24
+ backcolor .5 .5 .5 .5
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ close joinserver;
+ open createserver
+ }
+ }
+
+
+ // BACK BAR //
+
+ itemDef
+ {
+ name back
+ style 3
+ background "ui/assets/backarrow.tga"
+ rect 16 424 50 50
+ visible 1
+ action
+ {
+ play "sound/misc/menu4.wav";
+ close joinserver;
+ open main
+ }
+
+ mouseEnter
+ {
+ hide back;
+ show back_alt
+ }
+ }
+
+ itemDef
+ {
+ name back_alt
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/backarrow_alt.tga"
+ rect 16 424 50 50
+ backcolor 0 0 0 0
+ forecolor 1 1 1 1
+ visible 0
+ type ITEM_TYPE_BUTTON
+
+ text "Back"
+ textalign ITEM_ALIGN_LEFT
+ textaligny 36
+ textalignx 60
+ textscale .6
+
+ mouseExit
+ {
+ hide back_alt;
+ show back
+ }
+
+ action
+ {
+ play "sound/misc/menu4.wav";
+ close joinserver;
+ open main
+ }
+ }
+
+
+
+
+ itemDef
+ {
+ name accept
+ style 3
+ rect 574 424 50 50
+ background "ui/assets/forwardarrow.tga"
+ backcolor 0 0 0 0
+ forecolor 1 1 1 1
+ visible 1
+ mouseEnter
+ {
+ hide accept;
+ show accept_alt
+ }
+
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript JoinServer
+ }
+ }
+
+ itemDef
+ {
+ name accept_alt
+ style WINDOW_STYLE_SHADER
+ rect 574 424 50 50
+ background "ui/assets/forwardarrow_alt.tga"
+ backcolor 0 0 0 0
+ type ITEM_TYPE_BUTTON
+ forecolor 1 1 1 1
+ visible 0
+ type ITEM_TYPE_BUTTON
+
+ text "Join"
+ textalign ITEM_ALIGN_LEFT
+ textaligny 36
+ textalignx -55
+ textscale .6
+
+ mouseExit
+ {
+ hide accept_alt;
+ show accept
+ }
+
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript JoinServer
+ }
+ }
+ }
+}
diff --git a/ui/main.menu b/ui/main.menu
new file mode 100644
index 0000000..488fe69
--- /dev/null
+++ b/ui/main.menu
@@ -0,0 +1,163 @@
+#include "ui/menudef.h"
+
+{
+ assetGlobalDef
+ {
+ font "fonts/font" 26 // font
+ smallFont "fonts/smallfont" 20 // font
+ bigFont "fonts/bigfont" 34 // font
+ cursor "ui/assets/3_cursor3" // cursor
+ gradientBar "ui/assets/gradientbar2.tga" // gradient bar
+ itemFocusSound "sound/misc/menu2.wav" // sound for item getting focus (via keyboard or mouse )
+
+ fadeClamp 1.0 // sets the fadeup alpha
+ fadeCycle 1 // how often fade happens in milliseconds
+ fadeAmount 0.1 // amount to adjust alpha per cycle
+
+ shadowColor 0.1 0.1 0.1 0.25 // shadow color
+ }
+
+
+
+
+
+ menuDef
+ {
+ name main
+ fullScreen MENU_TRUE
+ rect 0 0 640 480 // Size and position of the menu
+ visible MENU_TRUE // Visible on open
+ focusColor 1 .75 0 1 // Menu focus color for text and items
+
+ onOpen { uiScript stopRefresh ; playlooped "sound/ui/heartbeat.wav" }
+ onESC { open quit_popmenu }
+
+ itemDef
+ {
+ name background
+ rect 0 0 640 480
+ style WINDOW_STYLE_SHADER
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+ background "ui/assets/mainmenu.jpg"
+ }
+
+ itemDef
+ {
+ name splashmodel
+ rect 0 0 640 480
+ type ITEM_TYPE_MODEL
+ style WINDOW_STYLE_EMPTY
+ asset_model "models/splash/splash_screen.md3"
+ model_fovx 32.0
+ model_fovy 24.0
+ model_angle 180
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name mainmenu
+ text "Play"
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ rect 472 20 128 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 128
+ textaligny 20
+ textscale .416
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ close main;
+ open joinserver
+ }
+ }
+
+ itemDef
+ {
+ name mainmenu
+ text "Options"
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ textscale .416
+ rect 472 40 128 20
+ textalignx 128
+ textaligny 20
+ textalign ITEM_ALIGN_RIGHT
+ backcolor 0 0 0 0
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open simple_options
+ }
+ }
+
+ itemDef
+ {
+ name mainmenu
+ text "Mods"
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ textscale .416
+ rect 472 60 128 20
+ textalignx 128
+ textaligny 20
+ textalign ITEM_ALIGN_RIGHT
+ backcolor 0 0 0 0
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open mod
+ }
+ }
+
+ itemDef
+ {
+ name mainmenu
+ text "Quit"
+ type ITEM_TYPE_BUTTON
+ style WINDOW_STYLE_EMPTY
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ rect 472 80 128 20
+ textalignx 128
+ textaligny 20
+ textscale .416
+ textalign ITEM_ALIGN_RIGHT
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ open quit_popmenu
+ }
+ }
+
+ itemDef
+ {
+ name copyright
+ text "Tremulous (C) 2005-2006 darklegion development"
+ style WINDOW_STYLE_EMPTY
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ textscale .25
+ rect 0 440 640 40
+ textalign 1
+ textaligny 32
+ textalignx 320
+ forecolor .75 .75 .75 .75
+ visible 1
+ decoration
+ }
+ }
+}
diff --git a/ui/menudef.h b/ui/menudef.h
new file mode 100644
index 0000000..dbd0996
--- /dev/null
+++ b/ui/menudef.h
@@ -0,0 +1,363 @@
+
+#define ITEM_TYPE_TEXT 0 // simple text
+#define ITEM_TYPE_BUTTON 1 // button, basically text with a border
+#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped
+#define ITEM_TYPE_CHECKBOX 3 // check box
+#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar
+#define ITEM_TYPE_SAYFIELD 5 // the chat field
+#define ITEM_TYPE_COMBO 6 // drop down list
+#define ITEM_TYPE_LISTBOX 7 // scrollable list
+#define ITEM_TYPE_MODEL 8 // model
+#define ITEM_TYPE_OWNERDRAW 9 // owner draw, name specs what it is
+#define ITEM_TYPE_NUMERICFIELD 10 // editable text, associated with a cvar
+#define ITEM_TYPE_SLIDER 11 // mouse speed, volume, etc.
+#define ITEM_TYPE_YESNO 12 // yes no cvar setting
+#define ITEM_TYPE_MULTI 13 // multiple list setting, enumerated
+#define ITEM_TYPE_BIND 14 // multiple list setting, enumerated
+
+#define ITEM_ALIGN_LEFT 0 // left alignment
+#define ITEM_ALIGN_CENTER 1 // center alignment
+#define ITEM_ALIGN_RIGHT 2 // right alignment
+
+#define ITEM_TEXTSTYLE_NORMAL 0 // normal text
+#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking
+#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing
+#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_NEON 7 // drop shadow ( need a color for this )
+
+#define WINDOW_BORDER_NONE 0 // no border
+#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel )
+#define WINDOW_BORDER_HORZ 2 // horizontal borders only
+#define WINDOW_BORDER_VERT 3 // vertical borders only
+#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars
+
+#define WINDOW_STYLE_EMPTY 0 // no background
+#define WINDOW_STYLE_FILLED 1 // filled with background color
+#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color
+#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color
+#define WINDOW_STYLE_TEAMCOLOR 4 // team color
+#define WINDOW_STYLE_CINEMATIC 5 // cinematic
+
+#define MENU_TRUE 1 // uh.. true
+#define MENU_FALSE 0 // and false
+
+#define HUD_VERTICAL 0x00
+#define HUD_HORIZONTAL 0x01
+
+// list box element types
+#define LISTBOX_TEXT 0x00
+#define LISTBOX_IMAGE 0x01
+
+// list feeders
+#define FEEDER_HEADS 0x00 // model heads
+#define FEEDER_MAPS 0x01 // text maps based on game type
+#define FEEDER_SERVERS 0x02 // servers
+#define FEEDER_CLANS 0x03 // clan names
+#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format
+#define FEEDER_ALIENTEAM_LIST 0x05 // red team members
+#define FEEDER_HUMANTEAM_LIST 0x06 // blue team members
+#define FEEDER_PLAYER_LIST 0x07 // players
+#define FEEDER_TEAM_LIST 0x08 // team members for team voting
+#define FEEDER_MODS 0x09 // team members for team voting
+#define FEEDER_DEMOS 0x0a // team members for team voting
+#define FEEDER_SCOREBOARD 0x0b // team members for team voting
+#define FEEDER_Q3HEADS 0x0c // model heads
+#define FEEDER_SERVERSTATUS 0x0d // server status
+#define FEEDER_FINDPLAYER 0x0e // find player
+#define FEEDER_CINEMATICS 0x0f // cinematics
+
+//TA: tremulous menus
+#define FEEDER_TREMTEAMS 0x10 //teams
+#define FEEDER_TREMALIENCLASSES 0x11 //alien classes
+#define FEEDER_TREMHUMANITEMS 0x12 //human items
+#define FEEDER_TREMHUMANARMOURYBUY 0x13 //human buy
+#define FEEDER_TREMHUMANARMOURYSELL 0x14 //human sell
+#define FEEDER_TREMALIENUPGRADE 0x15 //alien upgrade
+#define FEEDER_TREMALIENBUILD 0x16 //alien buildables
+#define FEEDER_TREMHUMANBUILD 0x17 //human buildables
+//TA: tremulous menus
+#define FEEDER_IGNORE_LIST 0x18 //ignored players
+
+// display flags
+#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001
+#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002
+#define CG_SHOW_ANYTEAMGAME 0x00000004
+#define CG_SHOW_HARVESTER 0x00000008
+#define CG_SHOW_ONEFLAG 0x00000010
+#define CG_SHOW_CTF 0x00000020
+#define CG_SHOW_OBELISK 0x00000040
+#define CG_SHOW_HEALTHCRITICAL 0x00000080
+#define CG_SHOW_SINGLEPLAYER 0x00000100
+#define CG_SHOW_TOURNAMENT 0x00000200
+#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400
+#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800
+#define CG_SHOW_LANPLAYONLY 0x00001000
+#define CG_SHOW_MINED 0x00002000
+#define CG_SHOW_HEALTHOK 0x00004000
+#define CG_SHOW_TEAMINFO 0x00008000
+#define CG_SHOW_NOTEAMINFO 0x00010000
+#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000
+#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000
+#define CG_SHOW_ANYNONTEAMGAME 0x00080000
+#define CG_SHOW_2DONLY 0x10000000
+
+
+#define UI_SHOW_LEADER 0x00000001
+#define UI_SHOW_NOTLEADER 0x00000002
+#define UI_SHOW_FAVORITESERVERS 0x00000004
+#define UI_SHOW_ANYNONTEAMGAME 0x00000008
+#define UI_SHOW_ANYTEAMGAME 0x00000010
+#define UI_SHOW_NEWHIGHSCORE 0x00000020
+#define UI_SHOW_DEMOAVAILABLE 0x00000040
+#define UI_SHOW_NEWBESTTIME 0x00000080
+#define UI_SHOW_FFA 0x00000100
+#define UI_SHOW_NOTFFA 0x00000200
+#define UI_SHOW_NETANYNONTEAMGAME 0x00000400
+#define UI_SHOW_NETANYTEAMGAME 0x00000800
+#define UI_SHOW_NOTFAVORITESERVERS 0x00001000
+
+#define UI_SHOW_VOTEACTIVE 0x00002000
+#define UI_SHOW_CANVOTE 0x00004000
+#define UI_SHOW_TEAMVOTEACTIVE 0x00008000
+#define UI_SHOW_CANTEAMVOTE 0x00010000
+
+#define UI_SHOW_NOTSPECTATING 0x00020000
+
+// owner draw types
+// ideally these should be done outside of this file but
+// this makes it much easier for the macro expansion to
+// convert them for the designers ( from the .menu files )
+#define CG_OWNERDRAW_BASE 1
+#define CG_PLAYER_ARMOR_ICON 1
+#define CG_PLAYER_ARMOR_VALUE 2
+#define CG_PLAYER_HEAD 3
+#define CG_PLAYER_HEALTH 4
+#define CG_PLAYER_HEALTH_BAR 92
+#define CG_PLAYER_HEALTH_CROSS 99
+#define CG_PLAYER_AMMO_ICON 5
+#define CG_PLAYER_AMMO_VALUE 6
+#define CG_PLAYER_CLIPS_VALUE 70
+#define CG_PLAYER_BUILD_TIMER 115
+#define CG_PLAYER_CREDITS_VALUE 71
+#define CG_PLAYER_BANK_VALUE 72
+#define CG_PLAYER_CREDITS_VALUE_NOPAD 106
+#define CG_PLAYER_BANK_VALUE_NOPAD 107
+#define CG_PLAYER_STAMINA 73
+#define CG_PLAYER_STAMINA_1 93
+#define CG_PLAYER_STAMINA_2 94
+#define CG_PLAYER_STAMINA_3 95
+#define CG_PLAYER_STAMINA_4 96
+#define CG_PLAYER_STAMINA_BOLT 97
+#define CG_PLAYER_BOOST_BOLT 112
+#define CG_PLAYER_CLIPS_RING 98
+#define CG_PLAYER_BUILD_TIMER_RING 113
+#define CG_PLAYER_SELECT 74
+#define CG_PLAYER_SELECTTEXT 75
+#define CG_PLAYER_WEAPONICON 111
+#define CG_PLAYER_WALLCLIMBING 103
+#define CG_PLAYER_BOOSTED 104
+#define CG_PLAYER_POISON_BARBS 105
+#define CG_PLAYER_ALIEN_SENSE 108
+#define CG_PLAYER_HUMAN_SCANNER 109
+#define CG_PLAYER_USABLE_BUILDABLE 110
+#define CG_SELECTEDPLAYER_HEAD 7
+#define CG_SELECTEDPLAYER_NAME 8
+#define CG_SELECTEDPLAYER_LOCATION 9
+#define CG_SELECTEDPLAYER_STATUS 10
+#define CG_SELECTEDPLAYER_WEAPON 11
+#define CG_SELECTEDPLAYER_POWERUP 12
+
+#define CG_FLAGCARRIER_HEAD 13
+#define CG_FLAGCARRIER_NAME 14
+#define CG_FLAGCARRIER_LOCATION 15
+#define CG_FLAGCARRIER_STATUS 16
+#define CG_FLAGCARRIER_WEAPON 17
+#define CG_FLAGCARRIER_POWERUP 18
+
+#define CG_PLAYER_ITEM 19
+#define CG_PLAYER_SCORE 20
+
+#define CG_BLUE_FLAGHEAD 21
+#define CG_BLUE_FLAGSTATUS 22
+#define CG_BLUE_FLAGNAME 23
+#define CG_RED_FLAGHEAD 24
+#define CG_RED_FLAGSTATUS 25
+#define CG_RED_FLAGNAME 26
+
+#define CG_BLUE_SCORE 27
+#define CG_RED_SCORE 28
+#define CG_RED_NAME 29
+#define CG_BLUE_NAME 30
+#define CG_HARVESTER_SKULLS 31 // only shows in harvester
+#define CG_ONEFLAG_STATUS 32 // only shows in one flag
+#define CG_PLAYER_LOCATION 33
+#define CG_TEAM_COLOR 34
+#define CG_CTF_POWERUP 35
+
+#define CG_AREA_POWERUP 36
+#define CG_AREA_LAGOMETER 37 // painted with old system
+#define CG_PLAYER_HASFLAG 38
+#define CG_GAME_TYPE 39 // not done
+
+#define CG_SELECTEDPLAYER_ARMOR 40
+#define CG_SELECTEDPLAYER_HEALTH 41
+#define CG_PLAYER_STATUS 42
+#define CG_FRAGGED_MSG 43 // painted with old system
+#define CG_PROXMINED_MSG 44 // painted with old system
+#define CG_AREA_FPSINFO 45 // painted with old system
+#define CG_GAME_STATUS 49
+#define CG_KILLER 50
+#define CG_PLAYER_ARMOR_ICON2D 51
+#define CG_PLAYER_AMMO_ICON2D 52
+#define CG_ACCURACY 53
+#define CG_ASSISTS 54
+#define CG_DEFEND 55
+#define CG_EXCELLENT 56
+#define CG_IMPRESSIVE 57
+#define CG_PERFECT 58
+#define CG_GAUNTLET 59
+#define CG_SPECTATORS 60
+#define CG_TEAMINFO 61
+#define CG_VOICE_HEAD 62
+#define CG_VOICE_NAME 63
+#define CG_PLAYER_HASFLAG2D 64
+#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester
+#define CG_CAPFRAGLIMIT 66
+#define CG_1STPLACE 67
+#define CG_2NDPLACE 68
+#define CG_CAPTURES 69
+
+//TA: loading screen
+#define CG_LOAD_LEVELSHOT 76
+#define CG_LOAD_MEDIA 77
+#define CG_LOAD_MEDIA_LABEL 78
+#define CG_LOAD_BUILDABLES 79
+#define CG_LOAD_BUILDABLES_LABEL 80
+#define CG_LOAD_CHARMODEL 81
+#define CG_LOAD_CHARMODEL_LABEL 82
+#define CG_LOAD_OVERALL 83
+#define CG_LOAD_LEVELNAME 84
+#define CG_LOAD_MOTD 85
+#define CG_LOAD_HOSTNAME 86
+
+#define CG_FPS 87
+#define CG_FPS_FIXED 100
+#define CG_TIMER 88
+#define CG_TIMER_MINS 101
+#define CG_TIMER_SECS 102
+#define CG_SNAPSHOT 89
+#define CG_LAGOMETER 90
+#define CG_PLAYER_CROSSHAIRNAMES 114
+#define CG_STAGE_REPORT_TEXT 116
+#define CG_DEMO_PLAYBACK 117
+#define CG_DEMO_RECORDING 118
+
+#define CG_CONSOLE 91
+#define CG_TUTORIAL 119
+#define CG_CLOCK 120
+
+
+
+#define UI_OWNERDRAW_BASE 200
+#define UI_HANDICAP 200
+#define UI_PLAYERMODEL 202
+#define UI_CLANNAME 203
+#define UI_CLANLOGO 204
+#define UI_GAMETYPE 205
+#define UI_MAPPREVIEW 206
+#define UI_SKILL 207
+#define UI_BLUETEAMNAME 208
+#define UI_REDTEAMNAME 209
+#define UI_BLUETEAM1 210
+#define UI_BLUETEAM2 211
+#define UI_BLUETEAM3 212
+#define UI_BLUETEAM4 213
+#define UI_BLUETEAM5 214
+#define UI_REDTEAM1 215
+#define UI_REDTEAM2 216
+#define UI_REDTEAM3 217
+#define UI_REDTEAM4 218
+#define UI_REDTEAM5 219
+#define UI_NETSOURCE 220
+#define UI_NETMAPPREVIEW 221
+#define UI_NETFILTER 222
+#define UI_TIER 223
+#define UI_OPPONENTMODEL 224
+#define UI_TIERMAP1 225
+#define UI_TIERMAP2 226
+#define UI_TIERMAP3 227
+#define UI_PLAYERLOGO 228
+#define UI_OPPONENTLOGO 229
+#define UI_PLAYERLOGO_METAL 230
+#define UI_OPPONENTLOGO_METAL 231
+#define UI_PLAYERLOGO_NAME 232
+#define UI_OPPONENTLOGO_NAME 233
+#define UI_TIER_MAPNAME 234
+#define UI_TIER_GAMETYPE 235
+#define UI_ALLMAPS_SELECTION 236
+#define UI_OPPONENT_NAME 237
+#define UI_VOTE_KICK 238
+#define UI_BOTNAME 239
+#define UI_BOTSKILL 240
+#define UI_REDBLUE 241
+#define UI_SELECTEDPLAYER 243
+#define UI_MAPCINEMATIC 244
+#define UI_NETGAMETYPE 245
+#define UI_NETMAPCINEMATIC 246
+#define UI_SERVERREFRESHDATE 247
+#define UI_SERVERMOTD 248
+#define UI_GLINFO 249
+#define UI_KEYBINDSTATUS 250
+#define UI_CLANCINEMATIC 251
+#define UI_MAP_TIMETOBEAT 252
+#define UI_JOINGAMETYPE 253
+#define UI_PREVIEWCINEMATIC 254
+#define UI_STARTMAPCINEMATIC 255
+#define UI_MAPS_SELECTION 256
+
+//TA:
+//#define UI_DIALOG 257
+#define UI_TEAMINFOPANE 258
+#define UI_ACLASSINFOPANE 259
+#define UI_AUPGRADEINFOPANE 260
+#define UI_HITEMINFOPANE 261
+#define UI_HBUYINFOPANE 262
+#define UI_HSELLINFOPANE 263
+#define UI_ABUILDINFOPANE 264
+#define UI_HBUILDINFOPANE 265
+
+#define UI_PLAYERLIST_SELECTION 266
+#define UI_TEAMLIST_SELECTION 267
+
+#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag
+#define VOICECHAT_OFFENSE "offense" // command someone to go on offense
+#define VOICECHAT_DEFEND "defend" // command someone to go on defense
+#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag
+#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam)
+#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one)
+#define VOICECHAT_FOLLOWME "followme" // command someone to follow you
+#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag
+#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier
+#define VOICECHAT_YES "yes" // yes, affirmative, etc.
+#define VOICECHAT_NO "no" // no, negative, etc.
+#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag
+#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense
+#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense
+#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming)
+#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere
+#define VOICECHAT_ONFOLLOW "onfollow" // I'm following
+#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier
+#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag
+#define VOICECHAT_INPOSITION "inposition" // I'm in position
+#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag
+#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack
+#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF)
+#define VOICECHAT_STARTLEADER "startleader" // I'm the leader
+#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership
+#define VOICECHAT_TRASH "trash" // lots of trash talk
+#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader
+#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense
+#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense
diff --git a/ui/menus.txt b/ui/menus.txt
new file mode 100644
index 0000000..3fa3dcf
--- /dev/null
+++ b/ui/menus.txt
@@ -0,0 +1,20 @@
+// menu defs
+//
+{
+ loadMenu { "ui/main.menu" }
+ loadMenu { "ui/joinserver.menu" }
+ loadMenu { "ui/options.menu" }
+ loadMenu { "ui/createserver.menu" }
+ loadMenu { "ui/mod.menu" }
+ loadMenu { "ui/credit.menu" }
+ loadMenu { "ui/connect.menu" }
+ loadMenu { "ui/password.menu" }
+ loadMenu { "ui/quit.menu" }
+ loadMenu { "ui/addfilter.menu" }
+ loadMenu { "ui/error.menu" }
+ loadMenu { "ui/drop.menu" }
+ loadMenu { "ui/serverinfo.menu" }
+ loadMenu { "ui/findplayer.menu" }
+ loadMenu { "ui/quitcredit.menu" }
+ loadMenu { "ui/createfavorite.menu" }
+}
diff --git a/ui/options.menu b/ui/options.menu
new file mode 100644
index 0000000..c6cc01c
--- /dev/null
+++ b/ui/options.menu
@@ -0,0 +1,287 @@
+#include "ui/menudef.h"
+
+{
+ \\ FRONT END OPTIONS BOX \\
+
+ menuDef
+ {
+ name "simple_options"
+ visible 0
+ fullscreen 0
+ rect 200 80 240 320
+ focusColor 1 .75 0 1
+ style 1
+ border 1
+ popup
+ onEsc
+ {
+ close simple_options;
+ open main
+ }
+
+ itemDef
+ {
+ name window
+ rect 0 0 240 320
+ style WINDOW_STYLE_FILLED
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+
+ border WINDOW_BORDER_FULL
+ borderSize 1.0
+ borderColor 0.5 0.5 0.5 1
+ }
+
+
+
+ itemDef
+ {
+ type ITEM_TYPE_EDITFIELD
+ style 0
+ text "Name:"
+ cvar "name"
+ maxchars 26
+ rect 50 20 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ }
+
+ itemDef
+ {
+ type ITEM_TYPE_MULTI
+ text "Video Quality:"
+ cvar "ui_glCustom"
+ cvarFloatList { "High Quality" 0 "Normal" 1 "Fast" 2 "Fastest" 3 "Custom" 4 }
+ rect 50 45 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript update "ui_glCustom"
+ }
+ }
+
+ itemDef
+ {
+ type ITEM_TYPE_MULTI
+ text "Video Mode:"
+ cvar "r_mode"
+ cvarFloatList { "320x240" 0 "400x300" 1 "512x384" 2 "640x480" 3
+ "800x600" 4 "960x720" 5 "1024x768" 6 "1152x864" 7
+ "1280x1024" 8 "1600x1200" 9 "2048x1536" 10 "856x480 wide screen" 11 }
+ rect 50 60 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript glCustom
+ }
+ }
+
+ itemDef
+ {
+ type ITEM_TYPE_SLIDER
+ text "Video Brightness:"
+ cvarfloat "r_gamma" 1 .5 2
+ rect 50 75 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ }
+
+
+
+ itemDef
+ {
+ type ITEM_TYPE_SLIDER
+ text "Effects Volume:"
+ cvarfloat "s_volume" 0.7 0 1
+ rect 50 110 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ }
+
+ itemDef
+ {
+ type ITEM_TYPE_SLIDER
+ text "Music Volume:"
+ cvarfloat "s_musicvolume" 0.25 0 1
+ rect 50 130 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ }
+
+ itemDef
+ {
+ type ITEM_TYPE_YESNO
+ text "OpenAL Sound:"
+ cvar "s_useOpenAL"
+ rect 50 145 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+
+
+ itemDef
+ {
+ type ITEM_TYPE_SLIDER
+ text "Mouse Sensitivity:"
+ cvarfloat "sensitivity" 5 1 30
+ rect 50 175 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ }
+
+ itemDef
+ {
+ type ITEM_TYPE_YESNO
+ text "Invert Mouse:"
+ cvar "ui_mousePitch"
+ rect 50 190 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ uiScript update ui_mousePitch
+ }
+ }
+
+
+
+ itemDef
+ {
+ type ITEM_TYPE_MULTI
+ text "Network Connection:"
+ cvar "rate"
+ cvarFloatList { "<=28.8k" 2500 "33.6k" 3000 "56k" 4000 "ISDN" 5000 "LAN/CABLE/xDSl" 25000 }
+ rect 50 220 192 20
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+ itemDef
+ {
+ type ITEM_TYPE_YESNO
+ text "Allow Auto Download:"
+ cvar "cl_allowDownload"
+ rect 50 235 192 15
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 64
+ textaligny 12
+ textscale .25
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
+
+ itemDef
+ {
+ text "APPLY"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ style WINDOW_STYLE_EMPTY
+ rect 95 255 30 20
+ textalign ITEM_ALIGN_CENTER
+ textalignx 15
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ exec "snd_restart" // includes vid_restart
+ }
+ }
+
+ itemDef
+ {
+ text "OK"
+ type ITEM_TYPE_BUTTON
+ textscale .25
+ style WINDOW_STYLE_EMPTY
+ rect 125 255 20 20
+ textalign ITEM_ALIGN_CENTER
+ textalignx 10
+ textaligny 15
+ forecolor 1 1 1 1
+ visible 1
+ action
+ {
+ play "sound/misc/menu1.wav";
+ close simple_options;
+ }
+ }
+
+
+ itemDef
+ {
+ text "For further options please use the in-game options menu"
+ style WINDOW_STYLE_EMPTY
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ textscale .25
+ rect 0 300 240 40
+ textalign ITEM_ALIGN_CENTER
+ textaligny 0
+ textalignx 120
+ forecolor 1 1 1 1
+ visible 1
+ decoration
+ }
+ }
+}
diff --git a/ui/quitcredit.menu b/ui/quitcredit.menu
new file mode 100644
index 0000000..679d6a8
--- /dev/null
+++ b/ui/quitcredit.menu
@@ -0,0 +1,430 @@
+#include "ui/menudef.h"
+
+{
+ \\ QUIT CREDIT \\
+
+ menuDef
+ {
+ name "quitCredit"
+ visible 0
+ fullscreen 1
+ rect 0 0 640 480
+ focusColor 1 .75 0 1
+ style 1
+ border 0
+ onEsc
+ {
+ uiScript "quit"
+ }
+
+ itemDef
+ {
+ name exitclickbox
+ style WINDOW_STYLE_SHADER
+ rect 0 0 640 480
+ type ITEM_TYPE_BUTTON
+ visible 1
+ backcolor 0 0 0 1
+ background "ui/assets/credits_splash.jpg"
+ action
+ {
+ close quitCredit;
+ uiScript "quit"
+ }
+ }
+
+ itemDef
+ {
+ name topstripe
+ style WINDOW_STYLE_FILLED
+ rect -5 -5 645 64
+ visible 1
+ backcolor 0 0 0 1
+
+ border WINDOW_BORDER_FULL
+ borderSize 1.5
+ borderColor 1 0 0 1
+ }
+
+ itemDef
+ {
+ name bottomstripe
+ style WINDOW_STYLE_FILLED
+ rect -5 416 645 485
+ visible 1
+ backcolor 0 0 0 1
+
+ border WINDOW_BORDER_FULL
+ borderSize 1.5
+ borderColor 1 0 0 1
+ }
+
+ itemDef
+ {
+ name "creditstitle"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 320 48 1 1
+ textalign ITEM_ALIGN_CENTER
+ textalignx 0
+ textaligny 0
+ textscale 0.75
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "CREDITS"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit1left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 96 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Tim 'Timbo' Angus"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit1right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 96 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Programming and Direction"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit2left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 128 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Nick 'jex' Jansens"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit2right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 128 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Mapping, texturing and 2D artwork"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit3left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 160 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Robin 'OverFlow' Marshall"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit3right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 160 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Modelling, animation and mapping"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit4left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 192 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Jan 'Stannum' van der Weg"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit4right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 192 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Texturing and mapping"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit5left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 224 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Mike 'Veda' McInerney"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit5right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 224 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Modelling, animation and texturing"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit6left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 256 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Gordon 'Godmil' Miller"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit6right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 256 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Mapping"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit7left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 288 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "'Who-[Soup]'"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit7right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 288 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Mapping"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit8left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 320 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Tristan 'jhrx' Blease"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit8right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 320 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Mapping"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit9left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 352 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Paul 'MoP' Greveson"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit9right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 352 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Modelling and texturing"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name "credit10left"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 10 384 1 1
+ textalign ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Chris 'Dolby' McCarthy"
+ forecolor 1 1 1 1
+ backcolor 1 0 0 1
+ visible 1
+ decoration
+ }
+ itemDef
+ {
+ name "credit10right"
+ group grpidcredit
+ style WINDOW_STYLE_EMPTY
+ rect 630 384 1 1
+ textalign ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 10
+ textscale 0.50
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ text "Sound"
+ forecolor 1 1 1 1
+ backcolor 0 1 0 1
+ visible 1
+ decoration
+ }
+ }
+}
diff --git a/ui/say.menu b/ui/say.menu
new file mode 100644
index 0000000..f6de3f9
--- /dev/null
+++ b/ui/say.menu
@@ -0,0 +1,91 @@
+#include "ui/menudef.h"
+
+{
+
+#define BORDER 10
+
+#define X BORDER
+#define Y BORDER
+//#define W (600-(2*BORDER))
+//#define H (480-(2*BORDER))
+#define W 580
+#define H 460
+
+ // Say to All
+ menuDef
+ {
+ name say
+ fullScreen MENU_FALSE
+ visible MENU_FALSE
+ rect X Y W H
+ //aspectBias ALIGN_LEFT
+ focusColor 1 1 1 1
+ style WINDOW_STYLE_EMPTY
+ onOpen
+ {
+ setfocus say_field;
+ }
+
+ itemDef
+ {
+ name say_field
+ type ITEM_TYPE_SAYFIELD
+ style WINDOW_STYLE_EMPTY
+ text "Say:"
+ cvar "ui_sayBuffer"
+ maxchars 128
+ //rect 0 0 W H
+ rect 0 230 W 20
+ //textalign ALIGN_LEFT
+ //textvalign VALIGN_CENTER
+ textstyle ITEM_TEXTSTYLE_SHADOWED
+ textscale .4
+ forecolor 0.93 0.93 0.92 1
+ visible MENU_TRUE
+ onTextEntry
+ {
+ uiScript Say;
+ close say;
+ }
+ }
+ }
+
+ // Say to Team
+ menuDef
+ {
+ name say_team
+ fullScreen MENU_FALSE
+ visible MENU_FALSE
+ rect X Y W H
+ //aspectBias ALIGN_LEFT
+ focusColor 1 1 1 1
+ style WINDOW_STYLE_EMPTY
+ onOpen
+ {
+ setfocus say_field
+ }
+
+ itemDef
+ {
+ name say_field
+ type ITEM_TYPE_SAYFIELD
+ style WINDOW_STYLE_EMPTY
+ text "Say to team:"
+ cvar "ui_sayBuffer"
+ maxchars 128
+ //rect 0 0 W H
+ rect 0 230 W 20
+ //textalign ALIGN_LEFT
+ //textvalign VALIGN_CENTER
+ textstyle ITEM_TEXTSTYLE_SHADOWED
+ textscale .4
+ forecolor 0.93 0.93 0.92 1
+ visible MENU_TRUE
+ onTextEntry
+ {
+ uiScript Say;
+ close say_team;
+ }
+ }
+ }
+}
diff --git a/ui/teamscore.menu b/ui/teamscore.menu
new file mode 100644
index 0000000..a0dad01
--- /dev/null
+++ b/ui/teamscore.menu
@@ -0,0 +1,305 @@
+#include "ui/menudef.h"
+
+{
+ \\ score_menu \\
+
+ menuDef
+ {
+ name "teamscore_menu"
+ visible 0
+ fullscreen 0
+ rect 0 0 640 480
+ focusColor 1 .75 0 1
+ style 0
+ border 1
+
+ // GAMETYPE BAR //
+
+ // TEAM NAME //
+
+ itemDef
+ {
+ name teamNameWindow
+ rect 14 78 612 30
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0 0 0 .5
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name alienteamname
+ text "Aliens"
+ textalign ITEM_ALIGN_LEFT
+ textscale .5
+ textaligny 26
+ rect 20 78 306 23
+ forecolor 1 1 1 1
+ decoration
+ visible 1
+ }
+
+ itemDef
+ {
+ name stagereport
+ align ITEM_ALIGN_CENTER
+ textscale 0.4
+ textaligny 24
+ rect 14 78 612 23
+ forecolor 1 1 1 1
+ decoration
+ visible 1
+ ownerdraw CG_STAGE_REPORT_TEXT
+ }
+
+ itemDef
+ {
+ name humanteamname
+ text "Humans"
+ textalign ITEM_ALIGN_RIGHT
+ textscale .5
+ textaligny 26
+ rect 620 78 0 23
+ forecolor 1 1 1 1
+ decoration
+ visible 1
+ }
+
+ // TEAM BARS //
+
+ itemDef
+ {
+ name leftteambar
+ rect 14 112 307 25
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0 0 0 .5
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name rightteambar
+ rect 320 112 306 25
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0 0 0 .5
+ visible 1
+ decoration
+ }
+
+
+ // TEAM HEADINGS //
+
+ itemDef
+ {
+ name leftteamheadings
+ text "Status Name Kills Time Ping"
+ textscale .25
+ style 0
+ rect 25 112 128 30
+ textalign 0
+ textalignx 0 // x alignment point for text
+ // use it to offset left/right text from the edge
+ // or to center the text on a different point
+ textaligny 18
+ backcolor 0 0 0 0
+ forecolor 1 .75 0 1
+ decoration
+ visible 1
+ }
+
+ itemDef
+ {
+ name rightteamheadings
+ text "Status Name Kills Time Ping"
+ textscale .25
+ style 0
+ rect 331 112 128 30
+ textalign 0
+ textalignx 0 // x alignment point for text
+ // use it to offset left/right text from the edge
+ // or to center the text on a different point
+ textaligny 18
+ backcolor 0 0 0 0
+ forecolor 1 .75 0 1
+ decoration
+ visible 1
+ }
+
+
+ // GRADIENT BACKGROUNDS //
+
+ itemDef
+ {
+ name window
+ rect 320 142 1 220
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0 0 0 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name window
+ rect 300 142 1 220
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name window
+ rect 606 142 1 220
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ visible 1
+ decoration
+ }
+
+
+ // LIST //
+
+ itemDef
+ {
+ name leftlist
+ rect 14 136 306 222
+ forecolor .75 .75 .75 1
+ visible 1
+ type ITEM_TYPE_LISTBOX
+ elementwidth 135
+ elementheight 20
+ textscale .25
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_ALIENTEAM_LIST
+ notselectable
+ columns 7
+ 5 15 1 ITEM_ALIGN_LEFT
+ 21 15 1 ITEM_ALIGN_LEFT
+ 7 30 5 ITEM_ALIGN_LEFT
+ 45 100 24 ITEM_ALIGN_LEFT
+ 172 20 4 ITEM_ALIGN_RIGHT
+ 209 20 4 ITEM_ALIGN_RIGHT
+ 247 20 4 ITEM_ALIGN_RIGHT
+ }
+
+ itemDef
+ {
+ name rightlist
+ rect 320 136 306 222
+ forecolor 1 1 1 1
+ visible 1
+ type ITEM_TYPE_LISTBOX
+ elementwidth 135
+ elementheight 20
+ textscale .25
+ elementtype LISTBOX_TEXT
+ feeder FEEDER_HUMANTEAM_LIST
+ notselectable
+ columns 7
+ 5 15 1 ITEM_ALIGN_LEFT
+ 21 15 1 ITEM_ALIGN_LEFT
+ 7 30 5 ITEM_ALIGN_LEFT
+ 45 100 24 ITEM_ALIGN_LEFT
+ 172 20 4 ITEM_ALIGN_RIGHT
+ 209 20 4 ITEM_ALIGN_RIGHT
+ 247 20 4 ITEM_ALIGN_RIGHT
+ }
+
+
+ // PLAYER LIST BORDER //
+
+ itemDef
+ {
+ name window
+ rect 14 141 612 221
+ style WINDOW_STYLE_EMPTY
+ border 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 1
+ backcolor 0 0 0 .5
+ visible 1
+ decoration
+ }
+
+
+ // spectators //
+
+ itemDef
+ {
+ name window
+ rect 14 366 612 24
+ style WINDOW_STYLE_FILLED
+ border 1
+ bordercolor .5 .5 .5 1
+ forecolor 1 1 1 .7
+ backcolor 0 0 0 .5
+ textscale .33
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name window
+ text "Spectating:"
+ textaligny 20
+ rect 19 366 82 24
+ style WINDOW_STYLE_FILLED
+ forecolor 1 1 1 1
+ textscale .33
+ textalignx 3
+ visible 1
+ decoration
+ }
+
+ itemDef
+ {
+ name window
+ rect 100 366 520 24
+ style WINDOW_STYLE_FILLED
+ forecolor 1 1 1 1
+ textscale .33
+ visible 1
+ ownerdraw CG_SPECTATORS
+ decoration
+ }
+
+ // WINNAR //
+
+ itemDef
+ {
+ name winner
+ rect 310 400 612 40
+ type 4
+ style 0
+ text ""
+ cvar ui_winner
+ maxPaintChars 24
+ textalign ITEM_ALIGN_CENTER
+ textaligny 20
+ textscale .5
+ forecolor 1 1 1 1
+ visible 1
+ decoration
+ }
+
+ }
+}
diff --git a/ui/tremulous.txt b/ui/tremulous.txt
new file mode 100644
index 0000000..4e094ad
--- /dev/null
+++ b/ui/tremulous.txt
@@ -0,0 +1,21 @@
+// menu defs
+//
+{
+ loadMenu { "ui/tremulous_teamselect.menu" }
+ loadMenu { "ui/tremulous_alienclass.menu" }
+ loadMenu { "ui/tremulous_humanitem.menu" }
+
+ loadMenu { "ui/tremulous_alienbuild.menu" }
+ loadMenu { "ui/tremulous_humanbuild.menu" }
+
+ loadMenu { "ui/tremulous_humanarmoury.menu" }
+
+ loadMenu { "ui/tremulous_humandialogs.menu" }
+ loadMenu { "ui/tremulous_aliendialogs.menu" }
+
+ loadMenu { "ui/tremulous_alienupgrade.menu" }
+
+ loadMenu { "ui/ptrc.menu" }
+
+ loadMenu { "ui/say.menu" }
+}
diff --git a/ui/tremulous_alien_builder_hud.menu b/ui/tremulous_alien_builder_hud.menu
new file mode 100644
index 0000000..bf75327
--- /dev/null
+++ b/ui/tremulous_alien_builder_hud.menu
@@ -0,0 +1,371 @@
+#include "ui/menudef.h"
+
+// team menu
+//
+// defines from ui_shared.h
+
+{
+ menuDef
+ {
+ name "alien_builder_hud"
+ fullScreen MENU_FALSE
+ visible MENU_TRUE
+ rect 0 0 640 480
+
+ //CONSOLE
+ itemDef
+ {
+ name "console"
+ rect 8 8 560 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.4
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CONSOLE
+ }
+
+ //TUTORIAL
+ itemDef
+ {
+ name "tutorial"
+ rect 8 250 640 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 0.35
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TUTORIAL
+ }
+
+ //LAGOMETER
+ itemDef
+ {
+ name "lagometer"
+ rect 596 68 32 20
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 0 0 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_LAGOMETER
+ }
+
+ //DEMO STATE
+ itemDef
+ {
+ name "demoRecording"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 0 0 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_RECORDING
+ background "ui/assets/neutral/circle.tga"
+ }
+ itemDef
+ {
+ name "demoPlayback"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_PLAYBACK
+ background "ui/assets/forwardarrow.tga"
+ }
+
+ //SELECT
+ itemDef
+ {
+ name "select"
+ rect 240 435 160 32
+ visible 0
+ decoration
+ ownerdraw CG_PLAYER_SELECT
+ }
+
+ //////////////////
+ //STATIC OBJECTS//
+ //////////////////
+
+ //LEFT RING CIRCLE
+ itemDef
+ {
+ name "left-ring-circle"
+ rect 47.5 410 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //LEFT ARM
+ itemDef
+ {
+ name "left-arm"
+ rect 77 404.75 104 52.5
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/alien/left-arm.tga"
+ }
+
+ //LEFT ARM CIRCLE
+ itemDef
+ {
+ name "left-arm-circle"
+ rect 150 417.5 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //RIGHT RING CIRCLE
+ itemDef
+ {
+ name "right-ring-circle"
+ rect 567 410 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //RIGHT ARM
+ itemDef
+ {
+ name "right-arm"
+ rect 459 404.75 104 52.5
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/alien/right-arm.tga"
+ }
+
+ ///////////////////
+ //DYNAMIC OBJECTS//
+ ///////////////////
+
+ //BOLT
+ itemDef
+ {
+ name "bolt"
+ rect 52.5 412.5 15 20
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ background "ui/assets/alien/bolt.tga"
+ ownerdraw CG_PLAYER_BOOST_BOLT
+ }
+
+ //CROSS
+ itemDef
+ {
+ name "cross"
+ rect 155 422.5 15 15
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/cross.tga"
+ }
+
+ //LEFT RING
+ itemDef
+ {
+ name "left-ring"
+ rect 7.25 369.5 90.5 106
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ background "ui/assets/alien/left-ring.tga"
+ ownerdraw CG_PLAYER_BOOSTED
+ }
+
+ //LEFT SPIKES
+ itemDef
+ {
+ name "left-spikes"
+ rect 18.5 381 59 83
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1.0
+ background "ui/assets/alien/left-spikes.tga"
+ ownerdraw CG_PLAYER_WALLCLIMBING
+ }
+
+ //RIGHT RING
+ itemDef
+ {
+ name "right-ring"
+ rect 542.25 369.5 90.5 106
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ background "ui/assets/alien/right-ring.tga"
+ ownerdraw CG_PLAYER_BOOSTED
+ }
+
+ //RIGHT SPIKES
+ itemDef
+ {
+ name "right-spikes"
+ rect 562.5 381 59 83
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1.0
+ background "ui/assets/alien/right-spikes.tga"
+ ownerdraw CG_PLAYER_WALLCLIMBING
+ }
+
+ //HEALTH
+ itemDef
+ {
+ name "health"
+ rect 78.5 421.5 60 15
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 .5
+ ownerdraw CG_PLAYER_HEALTH
+ }
+
+ //ALIEN CLASS ICON
+ itemDef
+ {
+ name "alien-icon"
+ rect 465 417.5 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.6
+ ownerdraw CG_PLAYER_WEAPONICON
+ }
+
+ //ORGANS
+ itemDef
+ {
+ name "organs"
+ rect 570.5 415.95 15 15
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 .5
+ ownerdraw CG_PLAYER_CREDITS_VALUE_NOPAD
+ }
+
+ //BUILD TIMER
+ itemDef
+ {
+ name "buildtimer"
+ rect 567 410 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 .5
+ ownerdraw CG_PLAYER_BUILD_TIMER
+ }
+
+ //BUILD POINTS
+ itemDef
+ {
+ name "build-points"
+ rect 483.5 421.5 60 15
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 .5
+ ownerdraw CG_PLAYER_AMMO_VALUE
+ }
+
+ //FPS
+ itemDef
+ {
+ name "fps"
+ rect 572 8 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_FPS
+ }
+
+ //TIMER
+ itemDef
+ {
+ name "timer"
+ rect 572 38 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TIMER
+ }
+
+ //CLOCK
+ itemDef
+ {
+ name "clock"
+ rect 572 90 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.25
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CLOCK
+ }
+
+ //ALIENSENSE
+ itemDef
+ {
+ name "aliensense"
+ rect 20 20 600 400
+ visible 1
+ decoration
+ ownerdraw CG_PLAYER_ALIEN_SENSE
+ }
+
+ //PLAYER NAME
+ itemDef
+ {
+ name "playername"
+ rect 200 275 240 25
+ visible 1
+ decoration
+ textScale .5
+ ownerdraw CG_PLAYER_CROSSHAIRNAMES
+ }
+ }
+}
diff --git a/ui/tremulous_alien_general_hud.menu b/ui/tremulous_alien_general_hud.menu
new file mode 100644
index 0000000..cc81600
--- /dev/null
+++ b/ui/tremulous_alien_general_hud.menu
@@ -0,0 +1,360 @@
+#include "ui/menudef.h"
+
+// team menu
+//
+// defines from ui_shared.h
+
+{
+ menuDef
+ {
+ name "alien_general_hud"
+ fullScreen MENU_FALSE
+ visible MENU_TRUE
+ rect 0 0 640 480
+
+ //CONSOLE
+ itemDef
+ {
+ name "console"
+ rect 8 8 560 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.4
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CONSOLE
+ }
+
+ //TUTORIAL
+ itemDef
+ {
+ name "tutorial"
+ rect 8 250 640 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 0.35
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TUTORIAL
+ }
+
+ //LAGOMETER
+ itemDef
+ {
+ name "lagometer"
+ rect 596 68 32 20
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 0 0 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_LAGOMETER
+ }
+
+ //DEMO STATE
+ itemDef
+ {
+ name "demoRecording"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 0 0 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_RECORDING
+ background "ui/assets/neutral/circle.tga"
+ }
+ itemDef
+ {
+ name "demoPlayback"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_PLAYBACK
+ background "ui/assets/forwardarrow.tga"
+ }
+
+ //SELECT
+ itemDef
+ {
+ name "select"
+ rect 240 435 160 32
+ visible 0
+ decoration
+ ownerdraw CG_PLAYER_SELECT
+ }
+
+ //////////////////
+ //STATIC OBJECTS//
+ //////////////////
+
+ //LEFT RING CIRCLE
+ itemDef
+ {
+ name "left-ring-circle"
+ rect 47.5 410 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //LEFT ARM
+ itemDef
+ {
+ name "left-arm"
+ rect 77 404.75 104 52.5
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/alien/left-arm.tga"
+ }
+
+ //LEFT ARM CIRCLE
+ itemDef
+ {
+ name "left-arm-circle"
+ rect 150 417.5 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //RIGHT RING CIRCLE
+ itemDef
+ {
+ name "right-ring-circle"
+ rect 567 410 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //RIGHT ARM
+ itemDef
+ {
+ name "right-arm"
+ rect 459 404.75 104 52.5
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/alien/right-arm.tga"
+ }
+
+ ///////////////////
+ //DYNAMIC OBJECTS//
+ ///////////////////
+
+ //BLOB
+ itemDef
+ {
+ name "blob"
+ rect 479 419 57 18
+ visible 1
+ forecolor 1.0 0.0 0.0 0.5
+ background "ui/assets/alien/tremublob.tga"
+ ownerdraw CG_PLAYER_POISON_BARBS
+ }
+
+ //BOLT
+ itemDef
+ {
+ name "bolt"
+ rect 52.5 412.5 15 20
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ background "ui/assets/alien/bolt.tga"
+ ownerdraw CG_PLAYER_BOOST_BOLT
+ }
+
+ //CROSS
+ itemDef
+ {
+ name "cross"
+ rect 155 422.5 15 15
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/cross.tga"
+ }
+
+ //LEFT RING
+ itemDef
+ {
+ name "left-ring"
+ rect 7.25 369.5 90.5 106
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ background "ui/assets/alien/left-ring.tga"
+ ownerdraw CG_PLAYER_BOOSTED
+ }
+
+ //LEFT SPIKES
+ itemDef
+ {
+ name "left-spikes"
+ rect 18.5 381 59 83
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1.0
+ background "ui/assets/alien/left-spikes.tga"
+ ownerdraw CG_PLAYER_WALLCLIMBING
+ }
+
+ //RIGHT RING
+ itemDef
+ {
+ name "right-ring"
+ rect 542.25 369.5 90.5 106
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.5
+ background "ui/assets/alien/right-ring.tga"
+ ownerdraw CG_PLAYER_BOOSTED
+ }
+
+ //RIGHT SPIKES
+ itemDef
+ {
+ name "right-spikes"
+ rect 562.5 381 59 83
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1.0
+ background "ui/assets/alien/right-spikes.tga"
+ ownerdraw CG_PLAYER_WALLCLIMBING
+ }
+
+ //HEALTH
+ itemDef
+ {
+ name "health"
+ rect 78.5 421.5 60 15
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 .5
+ ownerdraw CG_PLAYER_HEALTH
+ }
+
+ //ALIEN CLASS ICON
+ itemDef
+ {
+ name "alien-icon"
+ rect 465 417.5 25 25
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 0.6
+ ownerdraw CG_PLAYER_WEAPONICON
+ }
+
+ //ORGANS
+ itemDef
+ {
+ name "organs"
+ rect 570.5 415.95 15 15
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 .5
+ ownerdraw CG_PLAYER_CREDITS_VALUE_NOPAD
+ }
+
+ //FPS
+ itemDef
+ {
+ name "fps"
+ rect 572 8 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_FPS
+ }
+
+ //TIMER
+ itemDef
+ {
+ name "timer"
+ rect 572 38 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TIMER
+ }
+
+ //CLOCK
+ itemDef
+ {
+ name "clock"
+ rect 572 90 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1.0 0.0 0.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.25
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CLOCK
+ }
+
+ //ALIENSENSE
+ itemDef
+ {
+ name "aliensense"
+ rect 20 20 600 400
+ visible 1
+ decoration
+ ownerdraw CG_PLAYER_ALIEN_SENSE
+ }
+
+ //PLAYER NAME
+ itemDef
+ {
+ name "playername"
+ rect 200 275 240 25
+ visible 1
+ decoration
+ textScale .5
+ ownerdraw CG_PLAYER_CROSSHAIRNAMES
+ }
+ }
+}
diff --git a/ui/tremulous_default_hud.menu b/ui/tremulous_default_hud.menu
new file mode 100644
index 0000000..0eed640
--- /dev/null
+++ b/ui/tremulous_default_hud.menu
@@ -0,0 +1,165 @@
+#include "ui/menudef.h"
+
+{
+ menuDef
+ {
+ name "default_hud"
+ fullScreen MENU_FALSE
+ visible MENU_TRUE
+ rect 0 0 640 480
+
+ //CONSOLE
+ itemDef
+ {
+ name "console"
+ rect 8 8 560 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.4
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CONSOLE
+ }
+
+ //TUTORIAL
+ itemDef
+ {
+ name "tutorial"
+ rect 8 250 640 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 0.35
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TUTORIAL
+ }
+
+ //FPS
+ itemDef
+ {
+ name "fps"
+ rect 572 8 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_FPS
+ }
+ //TIMER
+ itemDef
+ {
+ name "timer"
+ rect 572 38 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TIMER
+ }
+ //CLOCK
+ itemDef
+ {
+ name "clock"
+ rect 572 90 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.25
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CLOCK
+ }
+
+ //SNAPSHOT
+ itemDef
+ {
+ name "snapshot"
+ rect 8 196 200 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.4
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_SNAPSHOT
+ }
+ //LAGOMETER
+ itemDef
+ {
+ name "lagometer"
+ rect 596 68 32 20
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_LAGOMETER
+ }
+ //DEMO STATE
+ itemDef
+ {
+ name "demoRecording"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 0 0 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_RECORDING
+ background "ui/assets/neutral/circle.tga"
+ }
+ itemDef
+ {
+ name "demoPlayback"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_PLAYBACK
+ background "ui/assets/forwardarrow.tga"
+ }
+
+ //PLAYER NAME
+ itemDef
+ {
+ name "playername"
+ rect 200 275 240 25
+ visible 1
+ decoration
+ textScale .5
+ ownerdraw CG_PLAYER_CROSSHAIRNAMES
+ }
+ }
+}
diff --git a/ui/tremulous_human_hud.menu b/ui/tremulous_human_hud.menu
new file mode 100644
index 0000000..8fe0095
--- /dev/null
+++ b/ui/tremulous_human_hud.menu
@@ -0,0 +1,462 @@
+#include "ui/menudef.h"
+
+// team menu
+//
+// defines from ui_shared.h
+
+{
+ menuDef
+ {
+ name "human_hud"
+ fullScreen MENU_FALSE
+ visible MENU_TRUE
+ rect 0 0 640 480
+
+ //CONSOLE
+ itemDef
+ {
+ name "console"
+ rect 8 8 560 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.4
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CONSOLE
+ }
+
+ //TUTORIAL
+ itemDef
+ {
+ name "tutorial"
+ rect 8 250 640 180
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 0.35
+ align ITEM_ALIGN_LEFT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TUTORIAL
+ }
+
+ //LAGOMETER
+ itemDef
+ {
+ name "lagometer"
+ rect 596 68 32 20
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 0 0.8 1 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_LAGOMETER
+ }
+
+ //DEMO STATE
+ itemDef
+ {
+ name "demoRecording"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 0 0 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_RECORDING
+ background "ui/assets/neutral/circle.tga"
+ }
+ itemDef
+ {
+ name "demoPlayback"
+ rect 596 120 32 32
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ textscale 0.3
+ textalignx 1
+ textaligny 0.5
+ ownerdraw CG_DEMO_PLAYBACK
+ background "ui/assets/forwardarrow.tga"
+ }
+
+ //FPS
+ itemDef
+ {
+ name "fps"
+ rect 572 8 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_FPS
+ }
+
+ //TIMER
+ itemDef
+ {
+ name "timer"
+ rect 572 38 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.3
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_TIMER
+ }
+
+ //CLOCK
+ itemDef
+ {
+ name "clock"
+ rect 572 90 56 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.25
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_CLOCK
+ }
+
+ //SNAPSHOT
+ itemDef
+ {
+ name "snapshot"
+ rect 8 196 200 22
+ style WINDOW_STYLE_EMPTY
+ visible 1
+ decoration
+ forecolor 1 1 1 1
+ align ITEM_ALIGN_RIGHT
+ textalignx 0
+ textaligny 18
+ textscale 0.4
+ textstyle ITEM_TEXTSTYLE_NORMAL
+ ownerdraw CG_SNAPSHOT
+ }
+
+ //////////////////
+ //STATIC OBJECTS//
+ //////////////////
+
+ //LEFT CIRCLE
+ itemDef
+ {
+ name "left-circle"
+ rect 35 417.5 25 25
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //LEFT ARM
+ itemDef
+ {
+ name "left-arm"
+ rect 68.25 420 94.5 35
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/human/left-arm.tga"
+ }
+
+ //CREDITS LABEL
+ itemDef
+ {
+ name "credits-label"
+ rect 508 403 7 7.5
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/human/credits.tga"
+ }
+
+ //RIGHT CIRCLE
+ itemDef
+ {
+ name "right-circle"
+ rect 580 417.5 25 25
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/circle.tga"
+ }
+
+ //RIGHT ARM
+ itemDef
+ {
+ name "right-arm"
+ rect 477.25 420 94.5 35
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/human/right-arm.tga"
+ }
+
+ //RIGHT CAP
+ itemDef
+ {
+ name "right-cap"
+ rect 500 400 80 15
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/human/right-cap.tga"
+ }
+
+ ///////////////////
+ //DYNAMIC OBJECTS//
+ ///////////////////
+
+ //BOLT
+ itemDef
+ {
+ name "bolt"
+ rect 40 420 15 20
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ background "ui/assets/human/bolt.tga"
+ ownerdraw CG_PLAYER_STAMINA_BOLT
+ }
+
+ //CROSS
+ itemDef
+ {
+ name "cross"
+ rect 137.5 430 15 15
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ background "ui/assets/neutral/cross.tga"
+ ownerdraw CG_PLAYER_HEALTH_CROSS
+ }
+
+ //STAMINA 1
+ itemDef
+ {
+ name "stamina1"
+ rect 34.5 403.5 9 11.5
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ background "ui/assets/human/stamina1.tga"
+ ownerdraw CG_PLAYER_STAMINA_1
+ }
+
+ //STAMINA 2
+ itemDef
+ {
+ name "stamina2"
+ rect 24 410.75 11.5 10.5
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ background "ui/assets/human/stamina2.tga"
+ ownerdraw CG_PLAYER_STAMINA_2
+ }
+
+ //STAMINA 3
+ itemDef
+ {
+ name "stamina3"
+ rect 20.75 423.5 10.5 7
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ background "ui/assets/human/stamina3.tga"
+ ownerdraw CG_PLAYER_STAMINA_3
+ }
+
+ //STAMINA 4
+ itemDef
+ {
+ name "stamina4"
+ rect 21 402.5 54 55
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ background "ui/assets/human/stamina4.tga"
+ ownerdraw CG_PLAYER_STAMINA_4
+ }
+
+ //RING
+ itemDef
+ {
+ name "ring"
+ // rect 20 402.5 55 55 // Guide for Stamina alignment
+ rect 565 402.5 55 55
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ background "ui/assets/human/ring.tga"
+ ownerdraw CG_PLAYER_CLIPS_RING
+ }
+
+ //CREDITS
+ itemDef
+ {
+ name "credits"
+ rect 515 402 45 11.25
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ ownerdraw CG_PLAYER_CREDITS_VALUE
+ }
+
+ //HEALTH
+ itemDef
+ {
+ name "health"
+ rect 67 430 60 15
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 .5
+ ownerdraw CG_PLAYER_HEALTH
+ }
+
+ //WEAPON ICON
+ itemDef
+ {
+ name "weapon"
+ rect 482.5 425 25 25
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ ownerdraw CG_PLAYER_WEAPONICON
+ }
+
+ //WEAPON SELECT TEXT
+ itemDef
+ {
+ name "selecttext"
+ rect 200 300 240 25
+ visible 1
+ decoration
+ textScale .5
+ ownerdraw CG_PLAYER_SELECTTEXT
+ }
+
+ //AMMO
+ itemDef
+ {
+ name "ammo"
+ rect 494 430 60 15
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 .5
+ ownerdraw CG_PLAYER_AMMO_VALUE
+ }
+
+ //CLIPS
+ itemDef
+ {
+ name "clips"
+ rect 538 423 60 15
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 .5
+ ownerdraw CG_PLAYER_CLIPS_VALUE
+ }
+
+ //BUILD TIMER
+ itemDef
+ {
+ name "buildtimer"
+ rect 580 417.5 25 25
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 .5
+ ownerdraw CG_PLAYER_BUILD_TIMER
+ }
+
+ //USABLE
+ itemDef
+ {
+ name "usable"
+ rect 307.5 380 25 25
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 .5
+ background "ui/assets/neutral/use.tga"
+ ownerdraw CG_PLAYER_USABLE_BUILDABLE
+ }
+
+ //SCANNER
+ itemDef
+ {
+ name "scanner"
+ rect 164 340 312 72
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 .5
+ background "ui/assets/human/scanner.tga"
+ ownerdraw CG_PLAYER_HUMAN_SCANNER
+ }
+
+ //INVENTORY
+ itemDef
+ {
+ name "inventory"
+ rect 232.5 425 175 25
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.5
+ ownerdraw CG_PLAYER_SELECT
+ }
+
+ //SELECTED
+ itemDef
+ {
+ name "selected"
+ rect 306 424 27 27
+ visible 1
+ decoration
+ forecolor 0.0 0.8 1.0 0.25
+ style WINDOW_STYLE_SHADER
+ background "ui/assets/neutral/selected.tga"
+ }
+
+ //PLAYER NAME
+ itemDef
+ {
+ name "playername"
+ rect 200 275 240 25
+ visible 1
+ decoration
+ textScale .5
+ ownerdraw CG_PLAYER_CROSSHAIRNAMES
+ }
+ }
+}